Merge "AAPT2: Allow <compatible-screens><screen> in AndroidManifest.xml"
diff --git a/Android.mk b/Android.mk
index 6831945..534b5e5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1239,6 +1239,31 @@
include $(BUILD_DROIDDOC)
+# ==== generates full navtree for resolving @links in ds postprocessing ====
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES)
+LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES)
+LOCAL_STATIC_JAVA_LIBRARIES:=$(framework_docs_LOCAL_STATIC_JAVA_LIBRARIES)
+LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES)
+LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS)
+LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH)
+LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_ADDITIONAL_JAVA_DIR)
+LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES)
+
+LOCAL_MODULE := ds-ref-navtree
+
+LOCAL_DROIDDOC_OPTIONS:= \
+ $(framework_docs_LOCAL_DROIDDOC_OPTIONS) \
+ -hdf android.whichdoc online \
+ -toroot / \
+ -atLinksNavtree \
+ -navtreeonly
+
+LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
+
+include $(BUILD_DROIDDOC)
+
# ==== site updates for docs (on the androiddevdocs app engine server) =======================
include $(CLEAR_VARS)
diff --git a/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
new file mode 100644
index 0000000..e644a1f
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.text;
+
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+import android.os.Bundle;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.StubActivity;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.style.ReplacementSpan;
+import android.util.ArraySet;
+
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Random;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized;
+
+@LargeTest
+@RunWith(Parameterized.class)
+public class DynamicLayoutPerfTest {
+
+ @Parameters(name = "{0}")
+ public static Collection cases() {
+ return Arrays.asList(new Object[][] {
+ { "0%", 0.0f},
+ { "1%", 0.01f},
+ { "5%", 0.05f},
+ { "30%", 0.3f},
+ { "100%", 1.0f},
+ });
+ }
+
+ private final String mMetricKey;
+ private final float mProbability;
+ public DynamicLayoutPerfTest(String metricKey, float probability) {
+ mMetricKey = metricKey;
+ mProbability = probability;
+ }
+
+ private static class MockReplacementSpan extends ReplacementSpan {
+ @Override
+ public int getSize(Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
+ return 10;
+ }
+
+ @Override
+ public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top,
+ int y, int bottom, Paint paint) {
+ }
+ }
+
+ @Rule
+ public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+
+ private final static String ALPHABETS = "abcdefghijklmnopqrstuvwxyz";
+
+ private SpannableStringBuilder getText() {
+ final long seed = 1234567890;
+ final Random r = new Random(seed);
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+
+ final int paragraphCount = 100;
+ for (int i = 0; i < paragraphCount; i++) {
+ final int wordCount = 5 + r.nextInt(20);
+ final boolean containsReplacementSpan = r.nextFloat() < mProbability;
+ final int replacedWordIndex = containsReplacementSpan ? r.nextInt(wordCount) : -1;
+ for (int j = 0; j < wordCount; j++) {
+ final int startIndex = builder.length();
+ final int wordLength = 1 + r.nextInt(10);
+ for (int k = 0; k < wordLength; k++) {
+ char c = ALPHABETS.charAt(r.nextInt(ALPHABETS.length()));
+ builder.append(c);
+ }
+ if (replacedWordIndex == j) {
+ builder.setSpan(new MockReplacementSpan(), startIndex,
+ builder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ builder.append(' ');
+ }
+ builder.append('\n');
+ }
+ return builder;
+ }
+
+ @Test
+ public void testGetBlocksAlwaysNeedToBeRedrawn() {
+ final SpannableStringBuilder text = getText();
+ final DynamicLayout layout = new DynamicLayout(text, new TextPaint(), 1000,
+ ALIGN_NORMAL, 0, 0, false);
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final int steps = 10;
+ while (state.keepRunning()) {
+ for (int i = 0; i < steps; i++) {
+ int offset = (text.length() * i) / steps;
+ text.insert(offset, "\n");
+ text.delete(offset, offset + 1);
+ final ArraySet<Integer> set = layout.getBlocksAlwaysNeedToBeRedrawn();
+ if (set != null) {
+ for (int j = 0; j < set.size(); j++) {
+ layout.getBlockIndex(set.valueAt(j));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
new file mode 100644
index 0000000..13a5e83
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Random;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.RenderNodeAnimator;
+import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.StubActivity;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.InstrumentationRegistry;
+
+@LargeTest
+@RunWith(Parameterized.class)
+public class EditTextLongTextPerfTest {
+ @Parameters(name = "{0}")
+ public static Collection cases() {
+ return Arrays.asList(new Object[][] {
+ { "10x30K", 10, 30000 },
+ { "300x1K", 300, 1000 },
+ });
+ }
+
+ private final String mMetricKey;
+ private final int mChars;
+ private final int mLines;
+
+ public EditTextLongTextPerfTest(String metricKey, int chars, int lines) {
+ mMetricKey = metricKey;
+ mChars = chars;
+ mLines = lines;
+ }
+
+ @Rule
+ public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private EditText setupEditText() {
+ final EditText editText = new EditText(mActivityRule.getActivity());
+
+ String alphabet = "abcdefghijklmnopqrstuvwxyz";
+ final long seed = 1234567890;
+ Random r = new Random(seed);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mLines; i++) {
+ for (int j = 0; j < mChars; j++) {
+ char c = alphabet.charAt(r.nextInt(alphabet.length()));
+ sb.append(c);
+ }
+ sb.append('\n');
+ }
+
+ final int height = 1000;
+ final int width = 1000;
+ editText.setHeight(height);
+ editText.setWidth(width);
+ editText.setLayoutParams(new ViewGroup.LayoutParams(width, height));
+
+ Activity activity = mActivityRule.getActivity();
+ activity.setContentView(editText);
+
+ editText.setText(sb.toString(), TextView.BufferType.EDITABLE);
+ editText.invalidate();
+ editText.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ editText.layout(0, 0, height, width);
+
+ return editText;
+ }
+
+ @Test
+ @UiThreadTest
+ public void testEditText() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final EditText editText = setupEditText();
+ final KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
+ final int steps = 100;
+ while (state.keepRunning()) {
+ for (int i = 0; i < steps; i++) {
+ int offset = (editText.getText().length() * i) / steps;
+ editText.setSelection(offset);
+ editText.bringPointIntoView(offset);
+ editText.onKeyDown(keyEvent.getKeyCode(), keyEvent);
+ editText.updateDisplayListIfDirty();
+ }
+ }
+ }
+}
diff --git a/api/current.txt b/api/current.txt
index 4c8e61a..dea5cec 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4923,6 +4923,7 @@
method public int describeContents();
method public java.lang.String getGroup();
method public android.graphics.drawable.Icon getLargeIcon();
+ method public java.lang.String getNotificationChannel();
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
method public void writeToParcel(android.os.Parcel, int);
@@ -5111,6 +5112,7 @@
method public android.app.Notification.Builder setActions(android.app.Notification.Action...);
method public android.app.Notification.Builder setAutoCancel(boolean);
method public android.app.Notification.Builder setCategory(java.lang.String);
+ method public android.app.Notification.Builder setChannel(java.lang.String);
method public android.app.Notification.Builder setChronometerCountDown(boolean);
method public android.app.Notification.Builder setColor(int);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
@@ -5259,6 +5261,7 @@
method public android.app.Notification.Builder extend(android.app.Notification.Builder);
method public java.util.List<android.app.Notification.Action> getActions();
method public android.graphics.Bitmap getBackground();
+ method public java.lang.String getBridgeTag();
method public int getContentAction();
method public int getContentIcon();
method public int getContentIconGravity();
@@ -5277,6 +5280,7 @@
method public java.util.List<android.app.Notification> getPages();
method public boolean getStartScrollBottom();
method public android.app.Notification.WearableExtender setBackground(android.graphics.Bitmap);
+ method public android.app.Notification.WearableExtender setBridgeTag(java.lang.String);
method public android.app.Notification.WearableExtender setContentAction(int);
method public android.app.Notification.WearableExtender setContentIcon(int);
method public android.app.Notification.WearableExtender setContentIconGravity(int);
@@ -5304,17 +5308,40 @@
field public static final int UNSET_ACTION_INDEX = -1; // 0xffffffff
}
+ public final class NotificationChannel implements android.os.Parcelable {
+ ctor public NotificationChannel(java.lang.String, java.lang.CharSequence);
+ ctor protected NotificationChannel(android.os.Parcel);
+ method public boolean canBypassDnd();
+ method public int describeContents();
+ method public android.net.Uri getDefaultRingtone();
+ method public java.lang.String getId();
+ method public int getImportance();
+ method public java.lang.CharSequence getName();
+ method public void setDefaultRingtone(android.net.Uri);
+ method public void setLights(boolean);
+ method public void setVibration(boolean);
+ method public boolean shouldShowLights();
+ method public boolean shouldVibrate();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
+ field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
+ }
+
public class NotificationManager {
method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
method public boolean areNotificationsEnabled();
method public void cancel(int);
method public void cancel(java.lang.String, int);
method public void cancelAll();
+ method public void createNotificationChannel(android.app.NotificationChannel);
+ method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String);
method public java.util.Map<java.lang.String, android.app.AutomaticZenRule> getAutomaticZenRules();
method public final int getCurrentInterruptionFilter();
method public int getImportance();
+ method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+ method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
method public boolean isNotificationPolicyAccessGranted();
method public void notify(int, android.app.Notification);
@@ -5323,6 +5350,7 @@
method public final void setInterruptionFilter(int);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
+ method public void updateNotificationChannel(android.app.NotificationChannel);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
@@ -13843,6 +13871,7 @@
method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void close();
+ method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
method public abstract android.hardware.camera2.CameraDevice getDevice();
method public abstract android.view.Surface getInputSurface();
method public abstract boolean isReprocessable();
@@ -14481,9 +14510,11 @@
public final class OutputConfiguration implements android.os.Parcelable {
ctor public OutputConfiguration(android.view.Surface);
ctor public OutputConfiguration(int, android.view.Surface);
+ ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>);
method public int describeContents();
method public android.view.Surface getSurface();
method public int getSurfaceGroupId();
+ method public void setDeferredSurface(android.view.Surface);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff
@@ -22584,8 +22615,8 @@
method public int getRepeatMode();
method public android.app.PendingIntent getSessionActivity();
method public android.media.session.MediaSession.Token getSessionToken();
- method public boolean getShuffleMode();
method public android.media.session.MediaController.TransportControls getTransportControls();
+ method public boolean isShuffleModeEnabled();
method public void registerCallback(android.media.session.MediaController.Callback);
method public void registerCallback(android.media.session.MediaController.Callback, android.os.Handler);
method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
@@ -22634,7 +22665,7 @@
method public void sendCustomAction(java.lang.String, android.os.Bundle);
method public void setRating(android.media.Rating);
method public void setRepeatMode(int);
- method public void setShuffleMode(boolean);
+ method public void setShuffleModeEnabled(boolean);
method public void skipToNext();
method public void skipToPrevious();
method public void skipToQueueItem(long);
@@ -22663,7 +22694,7 @@
method public void setRatingType(int);
method public void setRepeatMode(int);
method public void setSessionActivity(android.app.PendingIntent);
- method public void setShuffleMode(boolean);
+ method public void setShuffleModeEnabled(boolean);
field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
}
@@ -22687,7 +22718,7 @@
method public void onSeekTo(long);
method public void onSetRating(android.media.Rating);
method public void onSetRepeatMode(int);
- method public void onSetShuffleMode(boolean);
+ method public void onSetShuffleModeEnabled(boolean);
method public void onSkipToNext();
method public void onSkipToPrevious();
method public void onSkipToQueueItem(long);
@@ -22749,7 +22780,7 @@
field public static final long ACTION_SEEK_TO = 256L; // 0x100L
field public static final long ACTION_SET_RATING = 128L; // 0x80L
field public static final long ACTION_SET_REPEAT_MODE = 262144L; // 0x40000L
- field public static final long ACTION_SET_SHUFFLE_MODE = 524288L; // 0x80000L
+ field public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 524288L; // 0x80000L
field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L
@@ -31787,7 +31818,7 @@
field public static final java.lang.String TERTIARY_PHONE_TYPE = "tertiary_phone_type";
}
- public static final class ContactsContract.PhoneLookup implements android.provider.BaseColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.PhoneLookupColumns {
+ public static final class ContactsContract.PhoneLookup implements android.provider.BaseColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.PhoneLookupColumns {
field public static final android.net.Uri CONTENT_FILTER_URI;
field public static final android.net.Uri ENTERPRISE_CONTENT_FILTER_URI;
field public static final java.lang.String QUERY_PARAMETER_SIP_ADDRESS = "sip";
@@ -34791,6 +34822,8 @@
method public void onRequestConditions(int);
method public abstract void onSubscribe(android.net.Uri);
method public abstract void onUnsubscribe(android.net.Uri);
+ method public static final void requestRebind(android.content.ComponentName);
+ method public final void requestUnbind();
field public static final java.lang.String EXTRA_RULE_ID = "android.service.notification.extra.RULE_ID";
field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
@@ -35499,6 +35532,7 @@
method public static java.net.SocketAddress getsockname(java.io.FileDescriptor) throws android.system.ErrnoException;
method public static int gettid();
method public static int getuid();
+ method public static byte[] getxattr(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static java.lang.String if_indextoname(int);
method public static int if_nametoindex(java.lang.String);
method public static java.net.InetAddress inet_pton(int, java.lang.String);
@@ -35507,6 +35541,7 @@
method public static void lchown(java.lang.String, int, int) throws android.system.ErrnoException;
method public static void link(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static void listen(java.io.FileDescriptor, int) throws android.system.ErrnoException;
+ method public static java.lang.String[] listxattr(java.lang.String) throws android.system.ErrnoException;
method public static long lseek(java.io.FileDescriptor, long, int) throws android.system.ErrnoException;
method public static android.system.StructStat lstat(java.lang.String) throws android.system.ErrnoException;
method public static void mincore(long, long, byte[]) throws android.system.ErrnoException;
@@ -35533,6 +35568,7 @@
method public static int recvfrom(java.io.FileDescriptor, java.nio.ByteBuffer, int, java.net.InetSocketAddress) throws android.system.ErrnoException, java.net.SocketException;
method public static int recvfrom(java.io.FileDescriptor, byte[], int, int, int, java.net.InetSocketAddress) throws android.system.ErrnoException, java.net.SocketException;
method public static void remove(java.lang.String) throws android.system.ErrnoException;
+ method public static void removexattr(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static void rename(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static long sendfile(java.io.FileDescriptor, java.io.FileDescriptor, android.util.MutableLong, long) throws android.system.ErrnoException;
method public static int sendto(java.io.FileDescriptor, java.nio.ByteBuffer, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
@@ -35544,6 +35580,7 @@
method public static int setsid() throws android.system.ErrnoException;
method public static void setsockoptInt(java.io.FileDescriptor, int, int, int) throws android.system.ErrnoException;
method public static void setuid(int) throws android.system.ErrnoException;
+ method public static void setxattr(java.lang.String, java.lang.String, byte[], int) throws android.system.ErrnoException;
method public static void shutdown(java.io.FileDescriptor, int) throws android.system.ErrnoException;
method public static java.io.FileDescriptor socket(int, int, int) throws android.system.ErrnoException;
method public static void socketpair(int, int, int, java.io.FileDescriptor, java.io.FileDescriptor) throws android.system.ErrnoException;
@@ -36872,6 +36909,7 @@
field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
+ field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
field public static final java.lang.String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
@@ -36881,6 +36919,8 @@
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
+ field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
+ field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final java.lang.String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
field public static final java.lang.String KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL = "drop_video_call_when_answering_audio_call_bool";
field public static final java.lang.String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
@@ -36933,6 +36973,7 @@
field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
field public static final java.lang.String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
field public static final java.lang.String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
+ field public static final java.lang.String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
field public static final java.lang.String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
field public static final java.lang.String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool";
field public static final java.lang.String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
@@ -42152,7 +42193,11 @@
public final class PixelCopy {
method public static void request(android.view.SurfaceView, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.SurfaceView, android.graphics.Rect, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
method public static void request(android.view.Surface, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.Surface, android.graphics.Rect, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.Window, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.Window, android.graphics.Rect, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
field public static final int ERROR_DESTINATION_INVALID = 5; // 0x5
field public static final int ERROR_SOURCE_INVALID = 4; // 0x4
field public static final int ERROR_SOURCE_NO_DATA = 3; // 0x3
@@ -47296,8 +47341,8 @@
public class OverScroller {
ctor public OverScroller(android.content.Context);
ctor public OverScroller(android.content.Context, android.view.animation.Interpolator);
- ctor public OverScroller(android.content.Context, android.view.animation.Interpolator, float, float);
- ctor public OverScroller(android.content.Context, android.view.animation.Interpolator, float, float, boolean);
+ ctor public deprecated OverScroller(android.content.Context, android.view.animation.Interpolator, float, float);
+ ctor public deprecated OverScroller(android.content.Context, android.view.animation.Interpolator, float, float, boolean);
method public void abortAnimation();
method public boolean computeScrollOffset();
method public void fling(int, int, int, int, int, int, int, int);
@@ -55471,16 +55516,25 @@
public abstract class Provider extends java.util.Properties {
ctor protected Provider(java.lang.String, double, java.lang.String);
+ method public synchronized java.lang.Object compute(java.lang.Object, java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
+ method public synchronized java.lang.Object computeIfAbsent(java.lang.Object, java.util.function.Function<? super java.lang.Object, ? extends java.lang.Object>);
+ method public synchronized java.lang.Object computeIfPresent(java.lang.Object, java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
method public synchronized void forEach(java.util.function.BiConsumer<? super java.lang.Object, ? super java.lang.Object>);
method public java.lang.String getInfo();
method public java.lang.String getName();
+ method public synchronized java.lang.Object getOrDefault(java.lang.Object, java.lang.Object);
method public synchronized java.security.Provider.Service getService(java.lang.String, java.lang.String);
method public synchronized java.util.Set<java.security.Provider.Service> getServices();
method public double getVersion();
+ method public synchronized java.lang.Object merge(java.lang.Object, java.lang.Object, java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
method public synchronized java.lang.Object put(java.lang.Object, java.lang.Object);
method public synchronized void putAll(java.util.Map<?, ?>);
+ method public synchronized java.lang.Object putIfAbsent(java.lang.Object, java.lang.Object);
method protected synchronized void putService(java.security.Provider.Service);
method protected synchronized void removeService(java.security.Provider.Service);
+ method public synchronized boolean replace(java.lang.Object, java.lang.Object, java.lang.Object);
+ method public synchronized java.lang.Object replace(java.lang.Object, java.lang.Object);
+ method public synchronized void replaceAll(java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
}
public static class Provider.Service {
@@ -58589,6 +58643,33 @@
method public static java.lang.String toString(java.lang.Object[]);
}
+ public class Base64 {
+ method public static java.util.Base64.Decoder getDecoder();
+ method public static java.util.Base64.Encoder getEncoder();
+ method public static java.util.Base64.Decoder getMimeDecoder();
+ method public static java.util.Base64.Encoder getMimeEncoder();
+ method public static java.util.Base64.Encoder getMimeEncoder(int, byte[]);
+ method public static java.util.Base64.Decoder getUrlDecoder();
+ method public static java.util.Base64.Encoder getUrlEncoder();
+ }
+
+ public static class Base64.Decoder {
+ method public byte[] decode(byte[]);
+ method public byte[] decode(java.lang.String);
+ method public int decode(byte[], byte[]);
+ method public java.nio.ByteBuffer decode(java.nio.ByteBuffer);
+ method public java.io.InputStream wrap(java.io.InputStream);
+ }
+
+ public static class Base64.Encoder {
+ method public byte[] encode(byte[]);
+ method public int encode(byte[], byte[]);
+ method public java.nio.ByteBuffer encode(java.nio.ByteBuffer);
+ method public java.lang.String encodeToString(byte[]);
+ method public java.util.Base64.Encoder withoutPadding();
+ method public java.io.OutputStream wrap(java.io.OutputStream);
+ }
+
public class BitSet implements java.lang.Cloneable java.io.Serializable {
ctor public BitSet();
ctor public BitSet(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index f2049a92..0911f0ac 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5068,6 +5068,7 @@
method public int describeContents();
method public java.lang.String getGroup();
method public android.graphics.drawable.Icon getLargeIcon();
+ method public java.lang.String getNotificationChannel();
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
method public void writeToParcel(android.os.Parcel, int);
@@ -5258,6 +5259,7 @@
method public android.app.Notification.Builder setActions(android.app.Notification.Action...);
method public android.app.Notification.Builder setAutoCancel(boolean);
method public android.app.Notification.Builder setCategory(java.lang.String);
+ method public android.app.Notification.Builder setChannel(java.lang.String);
method public android.app.Notification.Builder setChronometerCountDown(boolean);
method public android.app.Notification.Builder setColor(int);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
@@ -5406,6 +5408,7 @@
method public android.app.Notification.Builder extend(android.app.Notification.Builder);
method public java.util.List<android.app.Notification.Action> getActions();
method public android.graphics.Bitmap getBackground();
+ method public java.lang.String getBridgeTag();
method public int getContentAction();
method public int getContentIcon();
method public int getContentIconGravity();
@@ -5424,6 +5427,7 @@
method public java.util.List<android.app.Notification> getPages();
method public boolean getStartScrollBottom();
method public android.app.Notification.WearableExtender setBackground(android.graphics.Bitmap);
+ method public android.app.Notification.WearableExtender setBridgeTag(java.lang.String);
method public android.app.Notification.WearableExtender setContentAction(int);
method public android.app.Notification.WearableExtender setContentIcon(int);
method public android.app.Notification.WearableExtender setContentIconGravity(int);
@@ -5451,17 +5455,48 @@
field public static final int UNSET_ACTION_INDEX = -1; // 0xffffffff
}
+ public final class NotificationChannel implements android.os.Parcelable {
+ ctor public NotificationChannel(java.lang.String, java.lang.CharSequence);
+ ctor protected NotificationChannel(android.os.Parcel);
+ method public boolean canBypassDnd();
+ method public int describeContents();
+ method public android.net.Uri getDefaultRingtone();
+ method public java.lang.String getId();
+ method public int getImportance();
+ method public int getLockscreenVisibility();
+ method public java.lang.CharSequence getName();
+ method public void populateFromXml(org.xmlpull.v1.XmlPullParser);
+ method public void setBypassDnd(boolean);
+ method public void setDefaultRingtone(android.net.Uri);
+ method public void setImportance(int);
+ method public void setLights(boolean);
+ method public void setLockscreenVisibility(int);
+ method public void setName(java.lang.CharSequence);
+ method public void setVibration(boolean);
+ method public boolean shouldShowLights();
+ method public boolean shouldVibrate();
+ method public org.json.JSONObject toJson() throws org.json.JSONException;
+ method public void writeToParcel(android.os.Parcel, int);
+ method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
+ field public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
+ field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
+ }
+
public class NotificationManager {
method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
method public boolean areNotificationsEnabled();
method public void cancel(int);
method public void cancel(java.lang.String, int);
method public void cancelAll();
+ method public void createNotificationChannel(android.app.NotificationChannel);
+ method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String);
method public java.util.Map<java.lang.String, android.app.AutomaticZenRule> getAutomaticZenRules();
method public final int getCurrentInterruptionFilter();
method public int getImportance();
+ method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+ method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
method public boolean isNotificationPolicyAccessGranted();
method public void notify(int, android.app.Notification);
@@ -5470,6 +5505,7 @@
method public final void setInterruptionFilter(int);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
+ method public void updateNotificationChannel(android.app.NotificationChannel);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
@@ -14291,6 +14327,7 @@
method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void close();
+ method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
method public abstract android.hardware.camera2.CameraDevice getDevice();
method public abstract android.view.Surface getInputSurface();
method public abstract boolean isReprocessable();
@@ -14931,10 +14968,12 @@
ctor public OutputConfiguration(int, android.view.Surface);
ctor public OutputConfiguration(android.view.Surface, int);
ctor public OutputConfiguration(int, android.view.Surface, int);
+ ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>);
method public int describeContents();
method public int getRotation();
method public android.view.Surface getSurface();
method public int getSurfaceGroupId();
+ method public void setDeferredSurface(android.view.Surface);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
field public static final int ROTATION_0 = 0; // 0x0
@@ -24180,8 +24219,8 @@
method public int getRepeatMode();
method public android.app.PendingIntent getSessionActivity();
method public android.media.session.MediaSession.Token getSessionToken();
- method public boolean getShuffleMode();
method public android.media.session.MediaController.TransportControls getTransportControls();
+ method public boolean isShuffleModeEnabled();
method public void registerCallback(android.media.session.MediaController.Callback);
method public void registerCallback(android.media.session.MediaController.Callback, android.os.Handler);
method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
@@ -24230,7 +24269,7 @@
method public void sendCustomAction(java.lang.String, android.os.Bundle);
method public void setRating(android.media.Rating);
method public void setRepeatMode(int);
- method public void setShuffleMode(boolean);
+ method public void setShuffleModeEnabled(boolean);
method public void skipToNext();
method public void skipToPrevious();
method public void skipToQueueItem(long);
@@ -24259,7 +24298,7 @@
method public void setRatingType(int);
method public void setRepeatMode(int);
method public void setSessionActivity(android.app.PendingIntent);
- method public void setShuffleMode(boolean);
+ method public void setShuffleModeEnabled(boolean);
field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
}
@@ -24283,7 +24322,7 @@
method public void onSeekTo(long);
method public void onSetRating(android.media.Rating);
method public void onSetRepeatMode(int);
- method public void onSetShuffleMode(boolean);
+ method public void onSetShuffleModeEnabled(boolean);
method public void onSkipToNext();
method public void onSkipToPrevious();
method public void onSkipToQueueItem(long);
@@ -24345,7 +24384,7 @@
field public static final long ACTION_SEEK_TO = 256L; // 0x100L
field public static final long ACTION_SET_RATING = 128L; // 0x80L
field public static final long ACTION_SET_REPEAT_MODE = 262144L; // 0x40000L
- field public static final long ACTION_SET_SHUFFLE_MODE = 524288L; // 0x80000L
+ field public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 524288L; // 0x80000L
field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L
@@ -34433,7 +34472,7 @@
field public static final java.lang.String STATE = "state";
}
- public static final class ContactsContract.PhoneLookup implements android.provider.BaseColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.PhoneLookupColumns {
+ public static final class ContactsContract.PhoneLookup implements android.provider.BaseColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.PhoneLookupColumns {
field public static final android.net.Uri CONTENT_FILTER_URI;
field public static final android.net.Uri ENTERPRISE_CONTENT_FILTER_URI;
field public static final java.lang.String QUERY_PARAMETER_SIP_ADDRESS = "sip";
@@ -37559,6 +37598,8 @@
method public void onRequestConditions(int);
method public abstract void onSubscribe(android.net.Uri);
method public abstract void onUnsubscribe(android.net.Uri);
+ method public static final void requestRebind(android.content.ComponentName);
+ method public final void requestUnbind();
field public static final java.lang.String EXTRA_RULE_ID = "android.service.notification.extra.RULE_ID";
field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
@@ -37644,6 +37685,7 @@
method public void onNotificationVisibilityChanged(java.lang.String, long, boolean);
field public static final int REASON_APP_CANCEL = 8; // 0x8
field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
+ field public static final int REASON_CHANNEL_BANNED = 17; // 0x11
field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2
field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3
field public static final int REASON_DELEGATE_CLICK = 1; // 0x1
@@ -38359,6 +38401,7 @@
method public static java.net.SocketAddress getsockname(java.io.FileDescriptor) throws android.system.ErrnoException;
method public static int gettid();
method public static int getuid();
+ method public static byte[] getxattr(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static java.lang.String if_indextoname(int);
method public static int if_nametoindex(java.lang.String);
method public static java.net.InetAddress inet_pton(int, java.lang.String);
@@ -38367,6 +38410,7 @@
method public static void lchown(java.lang.String, int, int) throws android.system.ErrnoException;
method public static void link(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static void listen(java.io.FileDescriptor, int) throws android.system.ErrnoException;
+ method public static java.lang.String[] listxattr(java.lang.String) throws android.system.ErrnoException;
method public static long lseek(java.io.FileDescriptor, long, int) throws android.system.ErrnoException;
method public static android.system.StructStat lstat(java.lang.String) throws android.system.ErrnoException;
method public static void mincore(long, long, byte[]) throws android.system.ErrnoException;
@@ -38393,6 +38437,7 @@
method public static int recvfrom(java.io.FileDescriptor, java.nio.ByteBuffer, int, java.net.InetSocketAddress) throws android.system.ErrnoException, java.net.SocketException;
method public static int recvfrom(java.io.FileDescriptor, byte[], int, int, int, java.net.InetSocketAddress) throws android.system.ErrnoException, java.net.SocketException;
method public static void remove(java.lang.String) throws android.system.ErrnoException;
+ method public static void removexattr(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static void rename(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static long sendfile(java.io.FileDescriptor, java.io.FileDescriptor, android.util.MutableLong, long) throws android.system.ErrnoException;
method public static int sendto(java.io.FileDescriptor, java.nio.ByteBuffer, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
@@ -38404,6 +38449,7 @@
method public static int setsid() throws android.system.ErrnoException;
method public static void setsockoptInt(java.io.FileDescriptor, int, int, int) throws android.system.ErrnoException;
method public static void setuid(int) throws android.system.ErrnoException;
+ method public static void setxattr(java.lang.String, java.lang.String, byte[], int) throws android.system.ErrnoException;
method public static void shutdown(java.io.FileDescriptor, int) throws android.system.ErrnoException;
method public static java.io.FileDescriptor socket(int, int, int) throws android.system.ErrnoException;
method public static void socketpair(int, int, int, java.io.FileDescriptor, java.io.FileDescriptor) throws android.system.ErrnoException;
@@ -39954,6 +40000,7 @@
field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
+ field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
field public static final java.lang.String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
@@ -39963,6 +40010,8 @@
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
+ field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
+ field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final java.lang.String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
field public static final java.lang.String KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL = "drop_video_call_when_answering_audio_call_bool";
field public static final java.lang.String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
@@ -40015,6 +40064,7 @@
field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
field public static final java.lang.String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
field public static final java.lang.String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
+ field public static final java.lang.String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
field public static final java.lang.String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
field public static final java.lang.String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool";
field public static final java.lang.String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
@@ -45323,7 +45373,11 @@
public final class PixelCopy {
method public static void request(android.view.SurfaceView, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.SurfaceView, android.graphics.Rect, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
method public static void request(android.view.Surface, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.Surface, android.graphics.Rect, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.Window, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.Window, android.graphics.Rect, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
field public static final int ERROR_DESTINATION_INVALID = 5; // 0x5
field public static final int ERROR_SOURCE_INVALID = 4; // 0x4
field public static final int ERROR_SOURCE_NO_DATA = 3; // 0x3
@@ -50824,8 +50878,8 @@
public class OverScroller {
ctor public OverScroller(android.content.Context);
ctor public OverScroller(android.content.Context, android.view.animation.Interpolator);
- ctor public OverScroller(android.content.Context, android.view.animation.Interpolator, float, float);
- ctor public OverScroller(android.content.Context, android.view.animation.Interpolator, float, float, boolean);
+ ctor public deprecated OverScroller(android.content.Context, android.view.animation.Interpolator, float, float);
+ ctor public deprecated OverScroller(android.content.Context, android.view.animation.Interpolator, float, float, boolean);
method public void abortAnimation();
method public boolean computeScrollOffset();
method public void fling(int, int, int, int, int, int, int, int);
@@ -58999,16 +59053,25 @@
public abstract class Provider extends java.util.Properties {
ctor protected Provider(java.lang.String, double, java.lang.String);
+ method public synchronized java.lang.Object compute(java.lang.Object, java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
+ method public synchronized java.lang.Object computeIfAbsent(java.lang.Object, java.util.function.Function<? super java.lang.Object, ? extends java.lang.Object>);
+ method public synchronized java.lang.Object computeIfPresent(java.lang.Object, java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
method public synchronized void forEach(java.util.function.BiConsumer<? super java.lang.Object, ? super java.lang.Object>);
method public java.lang.String getInfo();
method public java.lang.String getName();
+ method public synchronized java.lang.Object getOrDefault(java.lang.Object, java.lang.Object);
method public synchronized java.security.Provider.Service getService(java.lang.String, java.lang.String);
method public synchronized java.util.Set<java.security.Provider.Service> getServices();
method public double getVersion();
+ method public synchronized java.lang.Object merge(java.lang.Object, java.lang.Object, java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
method public synchronized java.lang.Object put(java.lang.Object, java.lang.Object);
method public synchronized void putAll(java.util.Map<?, ?>);
+ method public synchronized java.lang.Object putIfAbsent(java.lang.Object, java.lang.Object);
method protected synchronized void putService(java.security.Provider.Service);
method protected synchronized void removeService(java.security.Provider.Service);
+ method public synchronized boolean replace(java.lang.Object, java.lang.Object, java.lang.Object);
+ method public synchronized java.lang.Object replace(java.lang.Object, java.lang.Object);
+ method public synchronized void replaceAll(java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
}
public static class Provider.Service {
@@ -62117,6 +62180,33 @@
method public static java.lang.String toString(java.lang.Object[]);
}
+ public class Base64 {
+ method public static java.util.Base64.Decoder getDecoder();
+ method public static java.util.Base64.Encoder getEncoder();
+ method public static java.util.Base64.Decoder getMimeDecoder();
+ method public static java.util.Base64.Encoder getMimeEncoder();
+ method public static java.util.Base64.Encoder getMimeEncoder(int, byte[]);
+ method public static java.util.Base64.Decoder getUrlDecoder();
+ method public static java.util.Base64.Encoder getUrlEncoder();
+ }
+
+ public static class Base64.Decoder {
+ method public byte[] decode(byte[]);
+ method public byte[] decode(java.lang.String);
+ method public int decode(byte[], byte[]);
+ method public java.nio.ByteBuffer decode(java.nio.ByteBuffer);
+ method public java.io.InputStream wrap(java.io.InputStream);
+ }
+
+ public static class Base64.Encoder {
+ method public byte[] encode(byte[]);
+ method public int encode(byte[], byte[]);
+ method public java.nio.ByteBuffer encode(java.nio.ByteBuffer);
+ method public java.lang.String encodeToString(byte[]);
+ method public java.util.Base64.Encoder withoutPadding();
+ method public java.io.OutputStream wrap(java.io.OutputStream);
+ }
+
public class BitSet implements java.lang.Cloneable java.io.Serializable {
ctor public BitSet();
ctor public BitSet(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index a4753e4..5c06589 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4926,6 +4926,7 @@
method public int describeContents();
method public java.lang.String getGroup();
method public android.graphics.drawable.Icon getLargeIcon();
+ method public java.lang.String getNotificationChannel();
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
method public void writeToParcel(android.os.Parcel, int);
@@ -5114,6 +5115,7 @@
method public android.app.Notification.Builder setActions(android.app.Notification.Action...);
method public android.app.Notification.Builder setAutoCancel(boolean);
method public android.app.Notification.Builder setCategory(java.lang.String);
+ method public android.app.Notification.Builder setChannel(java.lang.String);
method public android.app.Notification.Builder setChronometerCountDown(boolean);
method public android.app.Notification.Builder setColor(int);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
@@ -5262,6 +5264,7 @@
method public android.app.Notification.Builder extend(android.app.Notification.Builder);
method public java.util.List<android.app.Notification.Action> getActions();
method public android.graphics.Bitmap getBackground();
+ method public java.lang.String getBridgeTag();
method public int getContentAction();
method public int getContentIcon();
method public int getContentIconGravity();
@@ -5280,6 +5283,7 @@
method public java.util.List<android.app.Notification> getPages();
method public boolean getStartScrollBottom();
method public android.app.Notification.WearableExtender setBackground(android.graphics.Bitmap);
+ method public android.app.Notification.WearableExtender setBridgeTag(java.lang.String);
method public android.app.Notification.WearableExtender setContentAction(int);
method public android.app.Notification.WearableExtender setContentIcon(int);
method public android.app.Notification.WearableExtender setContentIconGravity(int);
@@ -5307,17 +5311,40 @@
field public static final int UNSET_ACTION_INDEX = -1; // 0xffffffff
}
+ public final class NotificationChannel implements android.os.Parcelable {
+ ctor public NotificationChannel(java.lang.String, java.lang.CharSequence);
+ ctor protected NotificationChannel(android.os.Parcel);
+ method public boolean canBypassDnd();
+ method public int describeContents();
+ method public android.net.Uri getDefaultRingtone();
+ method public java.lang.String getId();
+ method public int getImportance();
+ method public java.lang.CharSequence getName();
+ method public void setDefaultRingtone(android.net.Uri);
+ method public void setLights(boolean);
+ method public void setVibration(boolean);
+ method public boolean shouldShowLights();
+ method public boolean shouldVibrate();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
+ field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
+ }
+
public class NotificationManager {
method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
method public boolean areNotificationsEnabled();
method public void cancel(int);
method public void cancel(java.lang.String, int);
method public void cancelAll();
+ method public void createNotificationChannel(android.app.NotificationChannel);
+ method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String);
method public java.util.Map<java.lang.String, android.app.AutomaticZenRule> getAutomaticZenRules();
method public final int getCurrentInterruptionFilter();
method public int getImportance();
+ method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+ method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
method public boolean isNotificationPolicyAccessGranted();
method public void notify(int, android.app.Notification);
@@ -5326,6 +5353,7 @@
method public final void setInterruptionFilter(int);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
+ method public void updateNotificationChannel(android.app.NotificationChannel);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
@@ -13859,6 +13887,7 @@
method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void close();
+ method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
method public abstract android.hardware.camera2.CameraDevice getDevice();
method public abstract android.view.Surface getInputSurface();
method public abstract boolean isReprocessable();
@@ -14497,9 +14526,11 @@
public final class OutputConfiguration implements android.os.Parcelable {
ctor public OutputConfiguration(android.view.Surface);
ctor public OutputConfiguration(int, android.view.Surface);
+ ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>);
method public int describeContents();
method public android.view.Surface getSurface();
method public int getSurfaceGroupId();
+ method public void setDeferredSurface(android.view.Surface);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff
@@ -22656,8 +22687,8 @@
method public int getRepeatMode();
method public android.app.PendingIntent getSessionActivity();
method public android.media.session.MediaSession.Token getSessionToken();
- method public boolean getShuffleMode();
method public android.media.session.MediaController.TransportControls getTransportControls();
+ method public boolean isShuffleModeEnabled();
method public void registerCallback(android.media.session.MediaController.Callback);
method public void registerCallback(android.media.session.MediaController.Callback, android.os.Handler);
method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
@@ -22706,7 +22737,7 @@
method public void sendCustomAction(java.lang.String, android.os.Bundle);
method public void setRating(android.media.Rating);
method public void setRepeatMode(int);
- method public void setShuffleMode(boolean);
+ method public void setShuffleModeEnabled(boolean);
method public void skipToNext();
method public void skipToPrevious();
method public void skipToQueueItem(long);
@@ -22735,7 +22766,7 @@
method public void setRatingType(int);
method public void setRepeatMode(int);
method public void setSessionActivity(android.app.PendingIntent);
- method public void setShuffleMode(boolean);
+ method public void setShuffleModeEnabled(boolean);
field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
}
@@ -22759,7 +22790,7 @@
method public void onSeekTo(long);
method public void onSetRating(android.media.Rating);
method public void onSetRepeatMode(int);
- method public void onSetShuffleMode(boolean);
+ method public void onSetShuffleModeEnabled(boolean);
method public void onSkipToNext();
method public void onSkipToPrevious();
method public void onSkipToQueueItem(long);
@@ -22821,7 +22852,7 @@
field public static final long ACTION_SEEK_TO = 256L; // 0x100L
field public static final long ACTION_SET_RATING = 128L; // 0x80L
field public static final long ACTION_SET_REPEAT_MODE = 262144L; // 0x40000L
- field public static final long ACTION_SET_SHUFFLE_MODE = 524288L; // 0x80000L
+ field public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 524288L; // 0x80000L
field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L
@@ -31864,7 +31895,7 @@
field public static final java.lang.String TERTIARY_PHONE_TYPE = "tertiary_phone_type";
}
- public static final class ContactsContract.PhoneLookup implements android.provider.BaseColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.PhoneLookupColumns {
+ public static final class ContactsContract.PhoneLookup implements android.provider.BaseColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.PhoneLookupColumns {
field public static final android.net.Uri CONTENT_FILTER_URI;
field public static final android.net.Uri ENTERPRISE_CONTENT_FILTER_URI;
field public static final java.lang.String QUERY_PARAMETER_SIP_ADDRESS = "sip";
@@ -34871,6 +34902,8 @@
method public void onRequestConditions(int);
method public abstract void onSubscribe(android.net.Uri);
method public abstract void onUnsubscribe(android.net.Uri);
+ method public static final void requestRebind(android.content.ComponentName);
+ method public final void requestUnbind();
field public static final java.lang.String EXTRA_RULE_ID = "android.service.notification.extra.RULE_ID";
field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
@@ -35579,6 +35612,7 @@
method public static java.net.SocketAddress getsockname(java.io.FileDescriptor) throws android.system.ErrnoException;
method public static int gettid();
method public static int getuid();
+ method public static byte[] getxattr(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static java.lang.String if_indextoname(int);
method public static int if_nametoindex(java.lang.String);
method public static java.net.InetAddress inet_pton(int, java.lang.String);
@@ -35587,6 +35621,7 @@
method public static void lchown(java.lang.String, int, int) throws android.system.ErrnoException;
method public static void link(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static void listen(java.io.FileDescriptor, int) throws android.system.ErrnoException;
+ method public static java.lang.String[] listxattr(java.lang.String) throws android.system.ErrnoException;
method public static long lseek(java.io.FileDescriptor, long, int) throws android.system.ErrnoException;
method public static android.system.StructStat lstat(java.lang.String) throws android.system.ErrnoException;
method public static void mincore(long, long, byte[]) throws android.system.ErrnoException;
@@ -35613,6 +35648,7 @@
method public static int recvfrom(java.io.FileDescriptor, java.nio.ByteBuffer, int, java.net.InetSocketAddress) throws android.system.ErrnoException, java.net.SocketException;
method public static int recvfrom(java.io.FileDescriptor, byte[], int, int, int, java.net.InetSocketAddress) throws android.system.ErrnoException, java.net.SocketException;
method public static void remove(java.lang.String) throws android.system.ErrnoException;
+ method public static void removexattr(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static void rename(java.lang.String, java.lang.String) throws android.system.ErrnoException;
method public static long sendfile(java.io.FileDescriptor, java.io.FileDescriptor, android.util.MutableLong, long) throws android.system.ErrnoException;
method public static int sendto(java.io.FileDescriptor, java.nio.ByteBuffer, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
@@ -35624,6 +35660,7 @@
method public static int setsid() throws android.system.ErrnoException;
method public static void setsockoptInt(java.io.FileDescriptor, int, int, int) throws android.system.ErrnoException;
method public static void setuid(int) throws android.system.ErrnoException;
+ method public static void setxattr(java.lang.String, java.lang.String, byte[], int) throws android.system.ErrnoException;
method public static void shutdown(java.io.FileDescriptor, int) throws android.system.ErrnoException;
method public static java.io.FileDescriptor socket(int, int, int) throws android.system.ErrnoException;
method public static void socketpair(int, int, int, java.io.FileDescriptor, java.io.FileDescriptor) throws android.system.ErrnoException;
@@ -36952,6 +36989,7 @@
field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
+ field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
field public static final java.lang.String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
@@ -36961,6 +36999,8 @@
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
+ field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
+ field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final java.lang.String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
field public static final java.lang.String KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL = "drop_video_call_when_answering_audio_call_bool";
field public static final java.lang.String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
@@ -37013,6 +37053,7 @@
field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
field public static final java.lang.String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
field public static final java.lang.String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
+ field public static final java.lang.String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
field public static final java.lang.String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
field public static final java.lang.String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool";
field public static final java.lang.String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
@@ -42091,6 +42132,7 @@
method public final void offsetLocation(float, float);
method public final void recycle();
method public final void setAction(int);
+ method public final void setActionButton(int);
method public final void setEdgeFlags(int);
method public final void setLocation(float, float);
method public final void setSource(int);
@@ -42235,7 +42277,11 @@
public final class PixelCopy {
method public static void request(android.view.SurfaceView, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.SurfaceView, android.graphics.Rect, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
method public static void request(android.view.Surface, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.Surface, android.graphics.Rect, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.Window, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
+ method public static void request(android.view.Window, android.graphics.Rect, android.graphics.Bitmap, android.view.PixelCopy.OnPixelCopyFinishedListener, android.os.Handler);
field public static final int ERROR_DESTINATION_INVALID = 5; // 0x5
field public static final int ERROR_SOURCE_INVALID = 4; // 0x4
field public static final int ERROR_SOURCE_NO_DATA = 3; // 0x3
@@ -44317,6 +44363,7 @@
method public void setMaxTextLength(int);
method public void setMovementGranularities(int);
method public void setMultiLine(boolean);
+ method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
method public void setPackageName(java.lang.CharSequence);
method public void setParent(android.view.View);
method public void setParent(android.view.View, int);
@@ -44522,6 +44569,7 @@
method public static android.view.accessibility.AccessibilityWindowInfo obtain();
method public static android.view.accessibility.AccessibilityWindowInfo obtain(android.view.accessibility.AccessibilityWindowInfo);
method public void recycle();
+ method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityWindowInfo> CREATOR;
field public static final int TYPE_ACCESSIBILITY_OVERLAY = 4; // 0x4
@@ -47381,8 +47429,8 @@
public class OverScroller {
ctor public OverScroller(android.content.Context);
ctor public OverScroller(android.content.Context, android.view.animation.Interpolator);
- ctor public OverScroller(android.content.Context, android.view.animation.Interpolator, float, float);
- ctor public OverScroller(android.content.Context, android.view.animation.Interpolator, float, float, boolean);
+ ctor public deprecated OverScroller(android.content.Context, android.view.animation.Interpolator, float, float);
+ ctor public deprecated OverScroller(android.content.Context, android.view.animation.Interpolator, float, float, boolean);
method public void abortAnimation();
method public boolean computeScrollOffset();
method public void fling(int, int, int, int, int, int, int, int);
@@ -55562,16 +55610,25 @@
public abstract class Provider extends java.util.Properties {
ctor protected Provider(java.lang.String, double, java.lang.String);
+ method public synchronized java.lang.Object compute(java.lang.Object, java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
+ method public synchronized java.lang.Object computeIfAbsent(java.lang.Object, java.util.function.Function<? super java.lang.Object, ? extends java.lang.Object>);
+ method public synchronized java.lang.Object computeIfPresent(java.lang.Object, java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
method public synchronized void forEach(java.util.function.BiConsumer<? super java.lang.Object, ? super java.lang.Object>);
method public java.lang.String getInfo();
method public java.lang.String getName();
+ method public synchronized java.lang.Object getOrDefault(java.lang.Object, java.lang.Object);
method public synchronized java.security.Provider.Service getService(java.lang.String, java.lang.String);
method public synchronized java.util.Set<java.security.Provider.Service> getServices();
method public double getVersion();
+ method public synchronized java.lang.Object merge(java.lang.Object, java.lang.Object, java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
method public synchronized java.lang.Object put(java.lang.Object, java.lang.Object);
method public synchronized void putAll(java.util.Map<?, ?>);
+ method public synchronized java.lang.Object putIfAbsent(java.lang.Object, java.lang.Object);
method protected synchronized void putService(java.security.Provider.Service);
method protected synchronized void removeService(java.security.Provider.Service);
+ method public synchronized boolean replace(java.lang.Object, java.lang.Object, java.lang.Object);
+ method public synchronized java.lang.Object replace(java.lang.Object, java.lang.Object);
+ method public synchronized void replaceAll(java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>);
}
public static class Provider.Service {
@@ -58680,6 +58737,33 @@
method public static java.lang.String toString(java.lang.Object[]);
}
+ public class Base64 {
+ method public static java.util.Base64.Decoder getDecoder();
+ method public static java.util.Base64.Encoder getEncoder();
+ method public static java.util.Base64.Decoder getMimeDecoder();
+ method public static java.util.Base64.Encoder getMimeEncoder();
+ method public static java.util.Base64.Encoder getMimeEncoder(int, byte[]);
+ method public static java.util.Base64.Decoder getUrlDecoder();
+ method public static java.util.Base64.Encoder getUrlEncoder();
+ }
+
+ public static class Base64.Decoder {
+ method public byte[] decode(byte[]);
+ method public byte[] decode(java.lang.String);
+ method public int decode(byte[], byte[]);
+ method public java.nio.ByteBuffer decode(java.nio.ByteBuffer);
+ method public java.io.InputStream wrap(java.io.InputStream);
+ }
+
+ public static class Base64.Encoder {
+ method public byte[] encode(byte[]);
+ method public int encode(byte[], byte[]);
+ method public java.nio.ByteBuffer encode(java.nio.ByteBuffer);
+ method public java.lang.String encodeToString(byte[]);
+ method public java.util.Base64.Encoder withoutPadding();
+ method public java.io.OutputStream wrap(java.io.OutputStream);
+ }
+
public class BitSet implements java.lang.Cloneable java.io.Serializable {
ctor public BitSet();
ctor public BitSet(int);
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 80af5ea..18ad43e 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -185,12 +185,7 @@
int main(int argc, char* const argv[])
{
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
- // Older kernels don't understand PR_SET_NO_NEW_PRIVS and return
- // EINVAL. Don't die on such kernels.
- if (errno != EINVAL) {
- LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
- return 12;
- }
+ LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
}
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
@@ -309,6 +304,5 @@
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
- return 10;
}
}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index beb550c..3c2efd8 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -68,15 +68,25 @@
static const char SYSTEM_DATA_DIR_PATH[] = "/data/system";
static const char SYSTEM_TIME_DIR_NAME[] = "time";
static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time";
+static const char CLOCK_FONT_ASSET[] = "images/clock_font.png";
+static const char CLOCK_FONT_ZIP_NAME[] = "clock_font.png";
static const char LAST_TIME_CHANGED_FILE_NAME[] = "last_time_change";
static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/system/time/last_time_change";
static const char ACCURATE_TIME_FLAG_FILE_NAME[] = "time_is_accurate";
static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_accurate";
// Java timestamp format. Don't show the clock if the date is before 2000-01-01 00:00:00.
static const long long ACCURATE_TIME_EPOCH = 946684800000;
+static constexpr char FONT_BEGIN_CHAR = ' ';
+static constexpr char FONT_END_CHAR = '~' + 1;
+static constexpr size_t FONT_NUM_CHARS = FONT_END_CHAR - FONT_BEGIN_CHAR + 1;
+static constexpr size_t FONT_NUM_COLS = 16;
+static constexpr size_t FONT_NUM_ROWS = FONT_NUM_CHARS / FONT_NUM_COLS;
+static const int TEXT_CENTER_VALUE = INT_MAX;
+static const int TEXT_MISSING_VALUE = INT_MIN;
static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
static const char PLAY_SOUND_PROP_NAME[] = "persist.sys.bootanim.play_sound";
static const int ANIM_ENTRY_NAME_MAX = 256;
+static constexpr size_t TEXT_POS_LEN_MAX = 16;
static const char BOOT_COMPLETED_PROP_NAME[] = "sys.boot_completed";
static const char BOOTREASON_PROP_NAME[] = "ro.boot.bootreason";
// bootreasons list in "system/core/bootstat/bootstat.cpp".
@@ -177,23 +187,22 @@
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
return NO_ERROR;
}
-status_t BootAnimation::initTexture(const Animation::Frame& frame)
+status_t BootAnimation::initTexture(FileMap* map, int* width, int* height)
{
- //StopWatch watch("blah");
-
SkBitmap bitmap;
- sk_sp<SkData> data = SkData::MakeWithoutCopy(frame.map->getDataPtr(),
- frame.map->getDataLength());
+ sk_sp<SkData> data = SkData::MakeWithoutCopy(map->getDataPtr(),
+ map->getDataLength());
sk_sp<SkImage> image = SkImage::MakeFromEncoded(data);
image->asLegacyBitmap(&bitmap, SkImage::kRO_LegacyBitmapMode);
// FileMap memory is never released until application exit.
// Release it now as the texture is already loaded and the memory used for
// the packed resource can be released.
- delete frame.map;
+ delete map;
// ensure we can call getPixels(). No need to call unlock, since the
// bitmap will go out of scope when we return from this method.
@@ -239,6 +248,9 @@
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
+ *width = w;
+ *height = h;
+
return NO_ERROR;
}
@@ -402,7 +414,6 @@
return false;
}
-
void BootAnimation::checkExit() {
// Allow surface flinger to gracefully request shutdown
char value[PROPERTY_VALUE_MAX];
@@ -413,6 +424,47 @@
}
}
+bool BootAnimation::validClock(const Animation::Part& part) {
+ return part.clockPosX != TEXT_MISSING_VALUE && part.clockPosY != TEXT_MISSING_VALUE;
+}
+
+bool parseTextCoord(const char* str, int* dest) {
+ if (strcmp("c", str) == 0) {
+ *dest = TEXT_CENTER_VALUE;
+ return true;
+ }
+
+ char* end;
+ int val = (int) strtol(str, &end, 0);
+ if (end == str || *end != '\0' || val == INT_MAX || val == INT_MIN) {
+ return false;
+ }
+ *dest = val;
+ return true;
+}
+
+// Parse two position coordinates. If only string is non-empty, treat it as the y value.
+void parsePosition(const char* str1, const char* str2, int* x, int* y) {
+ bool success = false;
+ if (strlen(str1) == 0) { // No values were specified
+ // success = false
+ } else if (strlen(str2) == 0) { // we have only one value
+ if (parseTextCoord(str1, y)) {
+ *x = TEXT_CENTER_VALUE;
+ success = true;
+ }
+ } else {
+ if (parseTextCoord(str1, x) && parseTextCoord(str2, y)) {
+ success = true;
+ }
+ }
+
+ if (!success) {
+ *x = TEXT_MISSING_VALUE;
+ *y = TEXT_MISSING_VALUE;
+ }
+}
+
// Parse a color represented as an HTML-style 'RRGGBB' string: each pair of
// characters in str is a hex number in [0, 255], which are converted to
// floating point values in the range [0.0, 1.0] and placed in the
@@ -459,24 +511,87 @@
return true;
}
-// The time glyphs are stored in a single image of height 64 pixels. Each digit is 40 pixels wide,
-// and the colon character is half that at 20 pixels. The glyph order is '0123456789:'.
-// We render 24 hour time.
-void BootAnimation::drawTime(const Texture& clockTex, const int yPos) {
- static constexpr char TIME_FORMAT[] = "%H:%M";
- static constexpr int TIME_LENGTH = sizeof(TIME_FORMAT);
+// The font image should be a 96x2 array of character images. The
+// columns are the printable ASCII characters 0x20 - 0x7f. The
+// top row is regular text; the bottom row is bold.
+status_t BootAnimation::initFont(Font* font, const char* fallback) {
+ status_t status = NO_ERROR;
- static constexpr int DIGIT_HEIGHT = 64;
- static constexpr int DIGIT_WIDTH = 40;
- static constexpr int COLON_WIDTH = DIGIT_WIDTH / 2;
- static constexpr int TIME_WIDTH = (DIGIT_WIDTH * 4) + COLON_WIDTH;
+ if (font->map != nullptr) {
+ glGenTextures(1, &font->texture.name);
+ glBindTexture(GL_TEXTURE_2D, font->texture.name);
- if (clockTex.h < DIGIT_HEIGHT || clockTex.w < (10 * DIGIT_WIDTH + COLON_WIDTH)) {
- ALOGE("Clock texture is too small; abandoning boot animation clock");
- mClockEnabled = false;
- return;
+ status = initTexture(font->map, &font->texture.w, &font->texture.h);
+
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ } else if (fallback != nullptr) {
+ status = initTexture(&font->texture, mAssets, fallback);
+ } else {
+ return NO_INIT;
}
+ if (status == NO_ERROR) {
+ font->char_width = font->texture.w / FONT_NUM_COLS;
+ font->char_height = font->texture.h / FONT_NUM_ROWS / 2; // There are bold and regular rows
+ }
+
+ return status;
+}
+
+void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {
+ glEnable(GL_BLEND); // Allow us to draw on top of the animation
+ glBindTexture(GL_TEXTURE_2D, font.texture.name);
+
+ const int len = strlen(str);
+ const int strWidth = font.char_width * len;
+
+ if (*x == TEXT_CENTER_VALUE) {
+ *x = (mWidth - strWidth) / 2;
+ } else if (*x < 0) {
+ *x = mWidth + *x - strWidth;
+ }
+ if (*y == TEXT_CENTER_VALUE) {
+ *y = (mHeight - font.char_height) / 2;
+ } else if (*y < 0) {
+ *y = mHeight + *y - font.char_height;
+ }
+
+ int cropRect[4] = { 0, 0, font.char_width, -font.char_height };
+
+ for (int i = 0; i < len; i++) {
+ char c = str[i];
+
+ if (c < FONT_BEGIN_CHAR || c > FONT_END_CHAR) {
+ c = '?';
+ }
+
+ // Crop the texture to only the pixels in the current glyph
+ const int charPos = (c - FONT_BEGIN_CHAR); // Position in the list of valid characters
+ const int row = charPos / FONT_NUM_COLS;
+ const int col = charPos % FONT_NUM_COLS;
+ cropRect[0] = col * font.char_width; // Left of column
+ cropRect[1] = row * font.char_height * 2; // Top of row
+ // Move down to bottom of regular (one char_heigh) or bold (two char_heigh) line
+ cropRect[1] += bold ? 2 * font.char_height : font.char_height;
+ glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect);
+
+ glDrawTexiOES(*x, *y, 0, font.char_width, font.char_height);
+
+ *x += font.char_width;
+ }
+
+ glDisable(GL_BLEND); // Return to the animation's default behaviour
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+// We render 24 hour time.
+void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) {
+ static constexpr char TIME_FORMAT[] = "%H:%M";
+ static constexpr int TIME_LENGTH = 6;
+
time_t rawtime;
time(&rawtime);
struct tm* timeInfo = localtime(&rawtime);
@@ -490,36 +605,9 @@
return;
}
- glEnable(GL_BLEND); // Allow us to draw on top of the animation
- glBindTexture(GL_TEXTURE_2D, clockTex.name);
-
- int xPos = (mWidth - TIME_WIDTH) / 2;
- int cropRect[4] = { 0, DIGIT_HEIGHT, DIGIT_WIDTH, -DIGIT_HEIGHT };
-
- for (int i = 0; i < TIME_LENGTH - 1; i++) {
- char c = timeBuff[i];
- int width = DIGIT_WIDTH;
- int pos = c - '0'; // Position in the character list
- if (pos < 0 || pos > 10) {
- continue;
- }
- if (c == ':') {
- width = COLON_WIDTH;
- }
-
- // Crop the texture to only the pixels in the current glyph
- int left = pos * DIGIT_WIDTH;
- cropRect[0] = left;
- cropRect[2] = width;
- glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect);
-
- glDrawTexiOES(xPos, yPos, 0, width, DIGIT_HEIGHT);
-
- xPos += width;
- }
-
- glDisable(GL_BLEND); // Return to the animation's default behaviour
- glBindTexture(GL_TEXTURE_2D, 0);
+ int x = xPos;
+ int y = yPos;
+ drawText(timeBuff, font, false, &x, &y);
}
bool BootAnimation::parseAnimationDesc(Animation& animation)
@@ -542,9 +630,10 @@
int height = 0;
int count = 0;
int pause = 0;
- int clockPosY = -1;
char path[ANIM_ENTRY_NAME_MAX];
char color[7] = "000000"; // default to black if unspecified
+ char clockPos1[TEXT_POS_LEN_MAX + 1] = "";
+ char clockPos2[TEXT_POS_LEN_MAX + 1] = "";
char pathType;
if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
@@ -552,15 +641,15 @@
animation.width = width;
animation.height = height;
animation.fps = fps;
- } else if (sscanf(l, " %c %d %d %s #%6s %d",
- &pathType, &count, &pause, path, color, &clockPosY) >= 4) {
- // ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPosY=%d", pathType, count, pause, path, color, clockPosY);
+ } else if (sscanf(l, " %c %d %d %s #%6s %16s %16s",
+ &pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) {
+ //ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPos1=%s, clockPos2=%s",
+ // pathType, count, pause, path, color, clockPos1, clockPos2);
Animation::Part part;
part.playUntilComplete = pathType == 'c';
part.count = count;
part.pause = pause;
part.path = path;
- part.clockPosY = clockPosY;
part.audioData = NULL;
part.animation = NULL;
if (!parseColor(color, part.backgroundColor)) {
@@ -569,6 +658,7 @@
part.backgroundColor[1] = 0.0f;
part.backgroundColor[2] = 0.0f;
}
+ parsePosition(clockPos1, clockPos2, &part.clockPosX, &part.clockPosY);
animation.parts.add(part);
}
else if (strcmp(l, "$SYSTEM") == 0) {
@@ -612,6 +702,14 @@
const String8 path(entryName.getPathDir());
const String8 leaf(entryName.getPathLeaf());
if (leaf.size() > 0) {
+ if (entryName == CLOCK_FONT_ZIP_NAME) {
+ FileMap* map = zip->createEntryFileMap(entry);
+ if (map) {
+ animation.clockFont.map = map;
+ }
+ continue;
+ }
+
for (size_t j = 0; j < pcount; j++) {
if (path == animation.parts[j].path) {
uint16_t method;
@@ -696,7 +794,7 @@
bool anyPartHasClock = false;
for (size_t i=0; i < animation->parts.size(); i++) {
- if(animation->parts[i].clockPosY >= 0) {
+ if(validClock(animation->parts[i])) {
anyPartHasClock = true;
break;
}
@@ -734,10 +832,11 @@
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- bool clockTextureInitialized = false;
+ bool clockFontInitialized = false;
if (mClockEnabled) {
- clockTextureInitialized = (initTexture(&mClock, mAssets, "images/clock64.png") == NO_ERROR);
- mClockEnabled = clockTextureInitialized;
+ clockFontInitialized =
+ (initFont(&animation->clockFont, CLOCK_FONT_ASSET) == NO_ERROR);
+ mClockEnabled = clockFontInitialized;
}
if (mClockEnabled && !updateIsTimeAccurate()) {
@@ -754,8 +853,8 @@
releaseAnimation(animation);
- if (clockTextureInitialized) {
- glDeleteTextures(1, &mClock.name);
+ if (clockFontInitialized) {
+ glDeleteTextures(1, &animation->clockFont.texture.name);
}
return false;
@@ -811,7 +910,8 @@
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
- initTexture(frame);
+ int w, h;
+ initTexture(frame.map, &w, &h);
}
const int xc = animationX + frame.trimX;
@@ -833,8 +933,8 @@
// which is equivalent to mHeight - (yc + frame.trimHeight)
glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight),
0, frame.trimWidth, frame.trimHeight);
- if (mClockEnabled && mTimeIsAccurate && part.clockPosY >= 0) {
- drawTime(mClock, part.clockPosY);
+ if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
+ drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
}
eglSwapBuffers(mDisplay, mSurface);
@@ -913,6 +1013,7 @@
Animation *animation = new Animation;
animation->fileName = fn;
animation->zip = zip;
+ animation->clockFont.map = nullptr;
mLoadedFiles.add(animation->fileName);
parseAnimationDesc(*animation);
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index fd497a3..42759f1 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -74,6 +74,13 @@
GLuint name;
};
+ struct Font {
+ FileMap* map;
+ Texture texture;
+ int char_width;
+ int char_height;
+ };
+
struct Animation {
struct Frame {
String8 name;
@@ -90,8 +97,12 @@
struct Part {
int count; // The number of times this part should repeat, 0 for infinite
int pause; // The number of frames to pause for at the end of this part
- int clockPosY; // The y position of the clock, in pixels, from the bottom of the
- // display (the clock is centred horizontally). -1 to disable the clock
+ int clockPosX; // The x position of the clock, in pixels. Positive values offset from
+ // the left of the screen, negative values offset from the right.
+ int clockPosY; // The y position of the clock, in pixels. Positive values offset from
+ // the bottom of the screen, negative values offset from the top.
+ // If either of the above are INT_MIN the clock is disabled, if INT_MAX
+ // the clock is centred on that axis.
String8 path;
String8 trimData;
SortedVector<Frame> frames;
@@ -108,13 +119,17 @@
String8 audioConf;
String8 fileName;
ZipFileRO* zip;
+ Font clockFont;
};
status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
- status_t initTexture(const Animation::Frame& frame);
+ status_t initTexture(FileMap* map, int* width, int* height);
+ status_t initFont(Font* font, const char* fallback);
bool android();
bool movie();
- void drawTime(const Texture& clockTex, const int yPos);
+ void drawText(const char* str, const Font& font, bool bold, int* x, int* y);
+ void drawClock(const Font& font, const int xPos, const int yPos);
+ bool validClock(const Animation::Part& part);
Animation* loadAnimation(const String8&);
bool playAnimation(const Animation&);
void releaseAnimation(Animation*) const;
@@ -127,7 +142,6 @@
sp<SurfaceComposerClient> mSession;
AssetManager mAssets;
Texture mAndroid[2];
- Texture mClock;
int mWidth;
int mHeight;
bool mUseNpotTextures = false;
diff --git a/cmds/bootanimation/audioplay.cpp b/cmds/bootanimation/audioplay.cpp
index 4983b9a..c546072 100644
--- a/cmds/bootanimation/audioplay.cpp
+++ b/cmds/bootanimation/audioplay.cpp
@@ -141,13 +141,27 @@
// configure audio source
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1};
+ // Determine channelMask from num_channels
+ SLuint32 channelMask;
+ switch (chunkFormat->num_channels) {
+ case 1:
+ channelMask = SL_SPEAKER_FRONT_CENTER;
+ break;
+ case 2:
+ channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+ break;
+ default:
+ // Default of 0 will derive mask from num_channels and log a warning.
+ channelMask = 0;
+ }
+
SLDataFormat_PCM format_pcm = {
SL_DATAFORMAT_PCM,
chunkFormat->num_channels,
chunkFormat->sample_rate * 1000, // convert to milliHz
chunkFormat->bits_per_sample,
16,
- SL_SPEAKER_FRONT_CENTER,
+ channelMask,
SL_BYTEORDER_LITTLEENDIAN
};
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index 2e9d0d6..0e0ecd0 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -230,8 +230,8 @@
}
@Override
- public void onShuffleModeChanged(boolean shuffleMode) throws RemoteException {
- System.out.println("onShuffleModeChanged " + shuffleMode);
+ public void onShuffleModeChanged(boolean enabled) throws RemoteException {
+ System.out.println("onShuffleModeChanged " + enabled);
}
void printUsageMessage() {
diff --git a/cmds/wm/src/com/android/commands/wm/Wm.java b/cmds/wm/src/com/android/commands/wm/Wm.java
index f7f7c88..383cd01 100644
--- a/cmds/wm/src/com/android/commands/wm/Wm.java
+++ b/cmds/wm/src/com/android/commands/wm/Wm.java
@@ -23,6 +23,7 @@
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.util.AndroidException;
import android.util.DisplayMetrics;
import android.view.Display;
@@ -201,9 +202,11 @@
try {
if (density > 0) {
// TODO(multidisplay): For now Configuration only applies to main screen.
- mWm.setForcedDisplayDensity(Display.DEFAULT_DISPLAY, density);
+ mWm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, density,
+ UserHandle.USER_CURRENT);
} else {
- mWm.clearForcedDisplayDensity(Display.DEFAULT_DISPLAY);
+ mWm.clearForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY,
+ UserHandle.USER_CURRENT);
}
} catch (RemoteException e) {
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index c4eaccc..163e7d2 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -53,7 +53,7 @@
import java.util.List;
/**
- * Accessibility services are intended to assist users with disabilities in using
+ * Accessibility services should only be used to assist users with disabilities in using
* Android devices and apps. They run in the background and receive callbacks by the system
* when {@link AccessibilityEvent}s are fired. Such events denote some state transition
* in the user interface, for example, the focus has changed, a button has been clicked,
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 419f723..f442ab1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -716,6 +716,11 @@
public static boolean hasMovementAnimations(int stackId) {
return stackId != PINNED_STACK_ID;
}
+
+ /** Returns true if the input stack and its content can affect the device orientation. */
+ public static boolean canSpecifyOrientation(int stackId) {
+ return stackId == HOME_STACK_ID || stackId == FULLSCREEN_WORKSPACE_STACK_ID;
+ }
}
/**
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index c7f19a2..f798512 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -3025,6 +3025,14 @@
reply.writeNoException();
return true;
}
+ case CAN_BYPASS_WORK_CHALLENGE: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final PendingIntent intent = PendingIntent.CREATOR.createFromParcel(data);
+ final boolean result = canBypassWorkChallenge(intent);
+ reply.writeNoException();
+ reply.writeInt(result ? 1 : 0);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -3234,7 +3242,7 @@
data.writeInt(0);
}
data.writeInt(userId);
- mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
+ mRemote.transact(START_ACTIVITY_WITH_CONFIG_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
reply.recycle();
@@ -5600,7 +5608,7 @@
data.writeInt(mode);
data.writeInt(sourceUserId);
data.writeInt(targetUserId);
- mRemote.transact(GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0);
+ mRemote.transact(GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
@@ -5620,7 +5628,7 @@
}
data.writeInt(mode);
data.writeInt(userId);
- mRemote.transact(REVOKE_URI_PERMISSION_TRANSACTION, data, reply, 0);
+ mRemote.transact(REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
@@ -7109,6 +7117,20 @@
reply.recycle();
return;
}
+ @Override
+ public boolean canBypassWorkChallenge(PendingIntent intent)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ intent.writeToParcel(data, 0);
+ mRemote.transact(CAN_BYPASS_WORK_CHALLENGE, data, reply, 0);
+ reply.readException();
+ final int result = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return result != 0;
+ }
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4ca1af8..cd4ba7c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -433,8 +433,10 @@
static final class NewIntentData {
List<ReferrerIntent> intents;
IBinder token;
+ boolean andPause;
public String toString() {
- return "NewIntentData{intents=" + intents + " token=" + token + "}";
+ return "NewIntentData{intents=" + intents + " token=" + token
+ + " andPause=" + andPause +"}";
}
}
@@ -751,10 +753,12 @@
configChanges, notResumed, config, overrideConfig, true, preserveWindow);
}
- public final void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token) {
+ public final void scheduleNewIntent(
+ List<ReferrerIntent> intents, IBinder token, boolean andPause) {
NewIntentData data = new NewIntentData();
data.intents = intents;
data.token = token;
+ data.andPause = andPause;
sendMessage(H.NEW_INTENT, data);
}
@@ -2792,24 +2796,34 @@
}
}
- public final void performNewIntents(IBinder token, List<ReferrerIntent> intents) {
- ActivityClientRecord r = mActivities.get(token);
- if (r != null) {
- final boolean resumed = !r.paused;
- if (resumed) {
- r.activity.mTemporaryPause = true;
- mInstrumentation.callActivityOnPause(r.activity);
- }
- deliverNewIntents(r, intents);
- if (resumed) {
- r.activity.performResume();
- r.activity.mTemporaryPause = false;
- }
+ void performNewIntents(IBinder token, List<ReferrerIntent> intents, boolean andPause) {
+ final ActivityClientRecord r = mActivities.get(token);
+ if (r == null) {
+ return;
+ }
+
+ final boolean resumed = !r.paused;
+ if (resumed) {
+ r.activity.mTemporaryPause = true;
+ mInstrumentation.callActivityOnPause(r.activity);
+ }
+ deliverNewIntents(r, intents);
+ if (resumed) {
+ r.activity.performResume();
+ r.activity.mTemporaryPause = false;
+ }
+
+ if (r.paused && andPause) {
+ // In this case the activity was in the paused state when we delivered the intent,
+ // to guarantee onResume gets called after onNewIntent we temporarily resume the
+ // activity and pause again as the caller wanted.
+ performResumeActivity(token, false, "performNewIntents");
+ performPauseActivityIfNeeded(r, "performNewIntents");
}
}
private void handleNewIntent(NewIntentData data) {
- performNewIntents(data.token, data.intents);
+ performNewIntents(data.token, data.intents, data.andPause);
}
public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 3063d98..28899e2 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -190,7 +190,8 @@
data.enforceInterface(IApplicationThread.descriptor);
List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
IBinder b = data.readStrongBinder();
- scheduleNewIntent(pi, b);
+ final boolean andPause = data.readInt() == 1;
+ scheduleNewIntent(pi, b, andPause);
return true;
}
@@ -915,12 +916,13 @@
data.recycle();
}
- public void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token)
+ public void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token, boolean andPause)
throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeTypedList(intents);
data.writeStrongBinder(token);
+ data.writeInt(andPause ? 1 : 0);
mRemote.transact(SCHEDULE_NEW_INTENT_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 03fcf47..462f66f 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1112,7 +1112,7 @@
if (cursor.moveToFirst()) {
int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
if (DownloadManager.STATUS_SUCCESSFUL == status) {
- return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, id);
+ return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
}
}
} finally {
@@ -1448,7 +1448,7 @@
* @hide
*/
public Uri getDownloadUri(long id) {
- return ContentUris.withAppendedId(mBaseUri, id);
+ return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
}
/**
@@ -1552,7 +1552,7 @@
// return content URI for cache download
long downloadId = getLong(getColumnIndex(Downloads.Impl._ID));
- return ContentUris.withAppendedId(mBaseUri, downloadId).toString();
+ return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, downloadId).toString();
}
private long getReason(int status) {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 1367280..6ea170e 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -194,7 +194,7 @@
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about using fragments, read the
- * <a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> developer guide.</p>
+ * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer guide.</p>
* </div>
*
* <a name="OlderPlatforms"></a>
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 099bae4..674c3f7 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -59,7 +59,7 @@
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about using fragments, read the
- * <a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> developer guide.</p>
+ * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer guide.</p>
* </div>
*
* While the FragmentManager API was introduced in
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index dfeea57..096f0cb 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -265,8 +265,20 @@
public void finishInstrumentation(IApplicationThread target,
int resultCode, Bundle results) throws RemoteException;
+ /**
+ * @return A copy of global {@link Configuration}, contains general settings for the entire
+ * system. Corresponds to the configuration of the default display.
+ * @throws RemoteException
+ */
public Configuration getConfiguration() throws RemoteException;
+
+ /**
+ * Updates global configuration and applies changes to the entire system.
+ * @param values Update values for global configuration.
+ * @throws RemoteException
+ */
public void updateConfiguration(Configuration values) throws RemoteException;
+
public void setRequestedOrientation(IBinder token,
int requestedOrientation) throws RemoteException;
public int getRequestedOrientation(IBinder token) throws RemoteException;
@@ -672,6 +684,18 @@
*/
public void setHasTopUi(boolean hasTopUi) throws RemoteException;
+ /**
+ * Returns if the target of the PendingIntent can be fired directly, without triggering
+ * a work profile challenge. This can happen if the PendingIntent is to start direct-boot
+ * aware activities, and the target user is in RUNNING_LOCKED state, i.e. we should allow
+ * direct-boot aware activity to bypass work challenge when the user hasn't unlocked yet.
+ * @param intent the {@link PendingIntent} to be tested.
+ * @return {@code true} if the intent should not trigger a work challenge, {@code false}
+ * otherwise.
+ * @throws RemoteException
+ */
+ public boolean canBypassWorkChallenge(PendingIntent intent) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -1062,6 +1086,7 @@
int SET_VR_THREAD_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 377;
int SET_RENDER_THREAD_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 378;
int SET_HAS_TOP_UI = IBinder.FIRST_CALL_TRANSACTION + 379;
+ int CAN_BYPASS_WORK_CHALLENGE = IBinder.FIRST_CALL_TRANSACTION + 380;
// Start of O transactions
int REQUEST_ACTIVITY_RELAUNCH = IBinder.FIRST_CALL_TRANSACTION+400;
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 7732157..131c1f9 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -67,7 +67,8 @@
List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed,
Configuration config, Configuration overrideConfig, boolean preserveWindow)
throws RemoteException;
- void scheduleNewIntent(List<ReferrerIntent> intent, IBinder token) throws RemoteException;
+ void scheduleNewIntent(
+ List<ReferrerIntent> intent, IBinder token, boolean andPause) throws RemoteException;
void scheduleDestroyActivity(IBinder token, boolean finished,
int configChanges) throws RemoteException;
void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index ee80ec3..28224e8 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -19,6 +19,7 @@
import android.app.ITransientNotification;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Intent;
@@ -57,6 +58,15 @@
int getImportance(String pkg, int uid);
int getPackageImportance(String pkg);
+ void createNotificationChannel(String pkg, in NotificationChannel channel);
+ void updateNotificationChannel(String pkg, in NotificationChannel channel);
+ void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
+ NotificationChannel getNotificationChannel(String pkg, String channelId);
+ NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId);
+ void deleteNotificationChannel(String pkg, String channelId);
+ ParceledListSlice getNotificationChannels(String pkg);
+ ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid);
+
// TODO: Remove this when callers have been migrated to the equivalent
// INotificationListener method.
StatusBarNotification[] getActiveNotifications(String callingPkg);
@@ -70,6 +80,8 @@
void requestBindListener(in ComponentName component);
void requestUnbindListener(in INotificationListener token);
+ void requestBindProvider(in ComponentName component);
+ void requestUnbindProvider(in IConditionProvider token);
void setNotificationsShownFromListener(in INotificationListener token, in String[] keys);
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 2a1e3c2..3b273bc 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -314,7 +314,7 @@
ArrayList<ReferrerIntent> intents = new ArrayList<>(1);
intents.add(new ReferrerIntent(intent, mParent.getPackageName()));
if (localLOGV) Log.v(TAG, r.id + ": new intent");
- mActivityThread.performNewIntents(r, intents);
+ mActivityThread.performNewIntents(r, intents, false /* andPause */);
r.intent = intent;
moveToState(r, mCurState);
if (mSingleMode) {
diff --git a/core/java/android/app/Notification.aidl b/core/java/android/app/Notification.aidl
index 3f1d113..9d8129c 100644
--- a/core/java/android/app/Notification.aidl
+++ b/core/java/android/app/Notification.aidl
@@ -17,4 +17,3 @@
package android.app;
parcelable Notification;
-parcelable Notification.Topic;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2028572..2595c45 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -992,6 +992,8 @@
private Icon mSmallIcon;
private Icon mLargeIcon;
+ private String mChannelId;
+
/**
* Structure to encapsulate a named action that can be shown as part of this notification.
* It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
@@ -1675,6 +1677,10 @@
}
color = parcel.readInt();
+
+ if (parcel.readInt() != 0) {
+ mChannelId = parcel.readString();
+ }
}
@Override
@@ -1780,6 +1786,8 @@
that.color = this.color;
+ that.mChannelId = this.mChannelId;
+
if (!heavy) {
that.lightenPayload(); // will clean out extras
}
@@ -2028,6 +2036,13 @@
}
parcel.writeInt(color);
+
+ if (mChannelId != null) {
+ parcel.writeInt(1);
+ parcel.writeString(mChannelId);
+ } else {
+ parcel.writeInt(0);
+ }
}
/**
@@ -2218,6 +2233,13 @@
}
/**
+ * Returns the id of the channel this notification posts to.
+ */
+ public String getNotificationChannel() {
+ return mChannelId;
+ }
+
+ /**
* The small icon representing this notification in the status bar and content view.
*
* @return the small icon representing this notification.
@@ -2406,6 +2428,14 @@
}
/**
+ * Specifies the channel the notification should be delivered on.
+ */
+ public Builder setChannel(String channelId) {
+ mN.mChannelId = channelId;
+ return this;
+ }
+
+ /**
* Add a timestamp pertaining to the notification (usually the time the event occurred).
*
* For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
@@ -2887,8 +2917,7 @@
}
/**
- * Make this notification automatically dismissed when the user touches it. The
- * PendingIntent set with {@link #setDeleteIntent} will be sent when this happens.
+ * Make this notification automatically dismissed when the user touches it.
*
* @see Notification#FLAG_AUTO_CANCEL
*/
@@ -2988,7 +3017,8 @@
/**
* Set this notification to be the group summary for a group of notifications.
* Grouped notifications may display in a cluster or stack on devices which
- * support such rendering. Requires a group key also be set using {@link #setGroup}.
+ * support such rendering. If thereRequires a group key also be set using {@link #setGroup}.
+ * The group summary may be suppressed if too few notifications are included in the group.
* @param isGroupSummary Whether this notification should be a group summary.
* @return this object for method chaining
*/
@@ -5834,6 +5864,7 @@
private static final String KEY_GRAVITY = "gravity";
private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
private static final String KEY_DISMISSAL_ID = "dismissalId";
+ private static final String KEY_BRIDGE_TAG = "bridgeTag";
// Flags bitwise-ored to mFlags
private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
@@ -5863,6 +5894,7 @@
private int mGravity = DEFAULT_GRAVITY;
private int mHintScreenTimeout;
private String mDismissalId;
+ private String mBridgeTag;
/**
* Create a {@link android.app.Notification.WearableExtender} with default
@@ -5900,6 +5932,7 @@
mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
+ mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
}
}
@@ -5953,6 +5986,9 @@
if (mDismissalId != null) {
wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
}
+ if (mBridgeTag != null) {
+ wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
+ }
builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
return builder;
@@ -5974,6 +6010,7 @@
that.mGravity = this.mGravity;
that.mHintScreenTimeout = this.mHintScreenTimeout;
that.mDismissalId = this.mDismissalId;
+ that.mBridgeTag = this.mBridgeTag;
return that;
}
@@ -6462,12 +6499,11 @@
}
/**
- * When you post a notification, if you set the dismissal id field, then when that
- * notification is canceled, notifications on other wearables and the paired Android phone
- * having that same dismissal id will also be canceled. Note that this only works if you
- * have notification bridge mode set to NO_BRIDGING in your Wear app manifest. See
+ * Sets the dismissal id for this notification. If a notification is posted with a
+ * dismissal id, then when that notification is canceled, notifications on other wearables
+ * and the paired Android phone having that same dismissal id will also be canceled. See
* <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
- * Notifications</a> for more information on how to use the bridge mode feature.
+ * Notifications</a> for more information.
* @param dismissalId the dismissal id of the notification.
* @return this object for method chaining
*/
@@ -6484,6 +6520,27 @@
return mDismissalId;
}
+ /**
+ * Sets a bridge tag for this notification. A bridge tag can be set for notifications
+ * posted from a phone to provide finer-grained control on what notifications are bridged
+ * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
+ * Features to Notifications</a> for more information.
+ * @param bridgeTag the bridge tag of the notification.
+ * @return this object for method chaining
+ */
+ public WearableExtender setBridgeTag(String bridgeTag) {
+ mBridgeTag = bridgeTag;
+ return this;
+ }
+
+ /**
+ * Returns the bridge tag of the notification.
+ * @return the bridge tag or null if not present.
+ */
+ public String getBridgeTag() {
+ return mBridgeTag;
+ }
+
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
diff --git a/core/java/android/app/NotificationChannel.aidl b/core/java/android/app/NotificationChannel.aidl
new file mode 100644
index 0000000..53e6863
--- /dev/null
+++ b/core/java/android/app/NotificationChannel.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable NotificationChannel;
\ No newline at end of file
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
new file mode 100644
index 0000000..530b8bb
--- /dev/null
+++ b/core/java/android/app/NotificationChannel.java
@@ -0,0 +1,382 @@
+package android.app;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.annotation.SystemApi;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.notification.NotificationListenerService;
+import android.text.TextUtils;
+
+import java.io.IOException;
+
+/**
+ * A representation of settings that apply to a collection of similarly themed notifications.
+ */
+public final class NotificationChannel implements Parcelable {
+
+ /**
+ * The id of the default channel for an app. All notifications posted without a notification
+ * channel specified are posted to this channel.
+ */
+ public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
+
+ private static final String TAG_CHANNEL = "channel";
+ private static final String ATT_NAME = "name";
+ private static final String ATT_ID = "id";
+ private static final String ATT_PRIORITY = "priority";
+ private static final String ATT_VISIBILITY = "visibility";
+ private static final String ATT_IMPORTANCE = "importance";
+ private static final String ATT_LIGHTS = "lights";
+ private static final String ATT_VIBRATION = "vibration";
+ private static final String ATT_DEFAULT_RINGTONE = "ringtone";
+
+ private static final int DEFAULT_VISIBILITY =
+ NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
+ private static final int DEFAULT_IMPORTANCE =
+ NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
+
+ private final String mId;
+ private CharSequence mName;
+ private int mImportance = DEFAULT_IMPORTANCE;
+ private boolean mBypassDnd;
+ private int mLockscreenVisibility = DEFAULT_VISIBILITY;
+ private Uri mRingtone;
+ private boolean mLights;
+ private boolean mVibration;
+
+ /**
+ * Creates a notification channel.
+ *
+ * @param id The id of the channel. Must be unique per package.
+ * @param name The user visible name of the channel.
+ */
+ public NotificationChannel(String id, CharSequence name) {
+ this.mId = id;
+ this.mName = name;
+ }
+
+ protected NotificationChannel(Parcel in) {
+ if (in.readByte() != 0) {
+ mId = in.readString();
+ } else {
+ mId = null;
+ }
+ mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mImportance = in.readInt();
+ mBypassDnd = in.readByte() != 0;
+ mLockscreenVisibility = in.readInt();
+ if (in.readByte() != 0) {
+ mRingtone = Uri.CREATOR.createFromParcel(in);
+ } else {
+ mRingtone = null;
+ }
+ mLights = in.readByte() != 0;
+ mVibration = in.readByte() != 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mId != null) {
+ dest.writeByte((byte) 1);
+ dest.writeString(mId);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ TextUtils.writeToParcel(mName, dest, flags);
+ dest.writeInt(mImportance);
+ dest.writeByte(mBypassDnd ? (byte) 1 : (byte) 0);
+ dest.writeInt(mLockscreenVisibility);
+ if (mRingtone != null) {
+ dest.writeByte((byte) 1);
+ mRingtone.writeToParcel(dest, 0);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ dest.writeByte(mLights ? (byte) 1 : (byte) 0);
+ dest.writeByte(mVibration ? (byte) 1 : (byte) 0);
+ }
+
+ // Only modifiable by users.
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void setName(CharSequence name) {
+ this.mName = name;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void setImportance(int importance) {
+ this.mImportance = importance;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void setBypassDnd(boolean bypassDnd) {
+ this.mBypassDnd = bypassDnd;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void setLockscreenVisibility(int lockscreenVisibility) {
+ this.mLockscreenVisibility = lockscreenVisibility;
+ }
+
+ // Modifiable by apps.
+
+ /**
+ * Sets the ringtone that should be played for notifications posted to this channel if
+ * the notifications don't supply a ringtone.
+ */
+ public void setDefaultRingtone(Uri defaultRingtone) {
+ this.mRingtone = defaultRingtone;
+ }
+
+ /**
+ * Sets whether notifications posted to this channel should display notification lights,
+ * on devices that support that feature.
+ */
+ public void setLights(boolean lights) {
+ this.mLights = lights;
+ }
+
+ /**
+ * Sets whether notification posted to this channel should vibrate, even if individual
+ * notifications are marked as having vibration.
+ */
+ public void setVibration(boolean vibration) {
+ this.mVibration = vibration;
+ }
+
+ /**
+ * Returns the id of this channel.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the user visible name of this channel.
+ */
+ public CharSequence getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the user specified importance {e.g. @link NotificationManager#IMPORTANCE_LOW} for
+ * notifications posted to this channel.
+ */
+ public int getImportance() {
+ return mImportance;
+ }
+
+ /**
+ * Whether or not notifications posted to this channel can bypass the Do Not Disturb
+ * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode.
+ */
+ public boolean canBypassDnd() {
+ return mBypassDnd;
+ }
+
+ /**
+ * Returns the notification sound for this channel.
+ */
+ public Uri getDefaultRingtone() {
+ return mRingtone;
+ }
+
+ /**
+ * Returns whether notifications posted to this channel trigger notification lights.
+ */
+ public boolean shouldShowLights() {
+ return mLights;
+ }
+
+ /**
+ * Returns whether notifications posted to this channel always vibrate.
+ */
+ public boolean shouldVibrate() {
+ return mVibration;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public int getLockscreenVisibility() {
+ return mLockscreenVisibility;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void populateFromXml(XmlPullParser parser) {
+ // Name and id are set in the constructor.
+ setImportance(safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE));
+ setBypassDnd(Notification.PRIORITY_DEFAULT
+ != safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
+ setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
+ setDefaultRingtone(safeUri(parser, ATT_DEFAULT_RINGTONE));
+ setLights(safeBool(parser, ATT_LIGHTS, false));
+ setVibration(safeBool(parser, ATT_VIBRATION, false));
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void writeXml(XmlSerializer out) throws IOException {
+ out.startTag(null, TAG_CHANNEL);
+ out.attribute(null, ATT_ID, getId());
+ out.attribute(null, ATT_NAME, getName().toString());
+ if (getImportance() != DEFAULT_IMPORTANCE) {
+ out.attribute(
+ null, ATT_IMPORTANCE, Integer.toString(getImportance()));
+ }
+ if (canBypassDnd()) {
+ out.attribute(
+ null, ATT_PRIORITY, Integer.toString(Notification.PRIORITY_MAX));
+ }
+ if (getLockscreenVisibility() != DEFAULT_VISIBILITY) {
+ out.attribute(null, ATT_VISIBILITY,
+ Integer.toString(getLockscreenVisibility()));
+ }
+ if (getDefaultRingtone() != null) {
+ out.attribute(null, ATT_DEFAULT_RINGTONE, getDefaultRingtone().toString());
+ }
+ if (shouldShowLights()) {
+ out.attribute(null, ATT_LIGHTS, Boolean.toString(shouldShowLights()));
+ }
+ if (shouldVibrate()) {
+ out.attribute(null, ATT_VIBRATION, Boolean.toString(shouldVibrate()));
+ }
+ out.endTag(null, TAG_CHANNEL);
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public JSONObject toJson() throws JSONException {
+ JSONObject record = new JSONObject();
+ record.put(ATT_ID, getId());
+ record.put(ATT_NAME, getName());
+ if (getImportance() != DEFAULT_IMPORTANCE) {
+ record.put(ATT_IMPORTANCE,
+ NotificationListenerService.Ranking.importanceToString(getImportance()));
+ }
+ if (canBypassDnd()) {
+ record.put(ATT_PRIORITY, Notification.PRIORITY_MAX);
+ }
+ if (getLockscreenVisibility() != DEFAULT_VISIBILITY) {
+ record.put(ATT_VISIBILITY, Notification.visibilityToString(getLockscreenVisibility()));
+ }
+ if (getDefaultRingtone() != null) {
+ record.put(ATT_DEFAULT_RINGTONE, getDefaultRingtone().toString());
+ }
+ record.put(ATT_LIGHTS, Boolean.toString(shouldShowLights()));
+ record.put(ATT_VIBRATION, Boolean.toString(shouldVibrate()));
+
+ return record;
+ }
+
+ private static Uri safeUri(XmlPullParser parser, String att) {
+ final String val = parser.getAttributeValue(null, att);
+ return val == null ? null : Uri.parse(val);
+ }
+
+ private static int safeInt(XmlPullParser parser, String att, int defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ return tryParseInt(val, defValue);
+ }
+
+ private static int tryParseInt(String value, int defValue) {
+ if (TextUtils.isEmpty(value)) return defValue;
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
+ final String value = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(value)) return defValue;
+ return Boolean.parseBoolean(value);
+ }
+
+ public static final Creator<NotificationChannel> CREATOR = new Creator<NotificationChannel>() {
+ @Override
+ public NotificationChannel createFromParcel(Parcel in) {
+ return new NotificationChannel(in);
+ }
+
+ @Override
+ public NotificationChannel[] newArray(int size) {
+ return new NotificationChannel[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NotificationChannel that = (NotificationChannel) o;
+
+ if (mImportance != that.mImportance) return false;
+ if (mBypassDnd != that.mBypassDnd) return false;
+ if (mLockscreenVisibility != that.mLockscreenVisibility) return false;
+ if (mLights != that.mLights) return false;
+ if (mVibration != that.mVibration) return false;
+ if (!mId.equals(that.mId)) return false;
+ if (mName != null ? !mName.equals(that.mName) : that.mName != null) return false;
+ return mRingtone != null ? mRingtone.equals(
+ that.mRingtone) : that.mRingtone == null;
+ }
+
+ @Override
+ public String toString() {
+ return "NotificationChannel{" +
+ "mId='" + mId + '\'' +
+ ", mName=" + mName +
+ ", mImportance=" + mImportance +
+ ", mBypassDnd=" + mBypassDnd +
+ ", mLockscreenVisibility=" + mLockscreenVisibility +
+ ", mRingtone='" + mRingtone + '\'' +
+ ", mLights=" + mLights +
+ ", mVibration=" + mVibration +
+ '}';
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mId.hashCode();
+ result = 31 * result + (mName != null ? mName.hashCode() : 0);
+ result = 31 * result + mImportance;
+ result = 31 * result + (mBypassDnd ? 1 : 0);
+ result = 31 * result + mLockscreenVisibility;
+ result = 31 * result + (mRingtone != null ? mRingtone.hashCode() : 0);
+ result = 31 * result + (mLights ? 1 : 0);
+ result = 31 * result + (mVibration ? 1 : 0);
+ return result;
+ }
+}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index ff514bd..39cd2b5 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -378,6 +378,66 @@
}
/**
+ * Creates a notification channel that notifications can be posted to.
+ */
+ public void createNotificationChannel(NotificationChannel channel) {
+ INotificationManager service = getService();
+ try {
+ service.createNotificationChannel(mContext.getPackageName(), channel);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the notification channel settings for a given channel id.
+ */
+ public NotificationChannel getNotificationChannel(String channelId) {
+ INotificationManager service = getService();
+ try {
+ return service.getNotificationChannel(mContext.getPackageName(), channelId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns all notification channels created by the calling app.
+ */
+ public List<NotificationChannel> getNotificationChannels() {
+ INotificationManager service = getService();
+ try {
+ return service.getNotificationChannels(mContext.getPackageName()).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Updates settings for a given channel.
+ */
+ public void updateNotificationChannel(NotificationChannel channel) {
+ INotificationManager service = getService();
+ try {
+ service.updateNotificationChannel(mContext.getPackageName(), channel);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Deletes the given notification channel.
+ */
+ public void deleteNotificationChannel(String channelId) {
+ INotificationManager service = getService();
+ try {
+ service.deleteNotificationChannel(mContext.getPackageName(), channelId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
*/
public ComponentName getEffectsSuppressor() {
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 0d4d007..b859418 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -580,37 +580,46 @@
command.run();
// Acquire the lock and wait for the event.
- synchronized (mLock) {
- try {
- // Wait for the event.
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (true) {
- // Drain the event queue
- while (!mEventQueue.isEmpty()) {
- AccessibilityEvent event = mEventQueue.remove(0);
- // Ignore events from previous interactions.
- if (event.getEventTime() < executionStartTimeMillis) {
- continue;
- }
- if (filter.accept(event)) {
- return event;
- }
- event.recycle();
+ try {
+ // Wait for the event.
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (true) {
+ List<AccessibilityEvent> localEvents = new ArrayList<>();
+ synchronized (mLock) {
+ localEvents.addAll(mEventQueue);
+ mEventQueue.clear();
+ }
+ // Drain the event queue
+ while (!localEvents.isEmpty()) {
+ AccessibilityEvent event = localEvents.remove(0);
+ // Ignore events from previous interactions.
+ if (event.getEventTime() < executionStartTimeMillis) {
+ continue;
}
- // Check if timed out and if not wait.
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- throw new TimeoutException("Expected event not received within: "
- + timeoutMillis + " ms.");
+ if (filter.accept(event)) {
+ return event;
}
- try {
- mLock.wait(remainingTimeMillis);
- } catch (InterruptedException ie) {
- /* ignore */
+ event.recycle();
+ }
+ // Check if timed out and if not wait.
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new TimeoutException("Expected event not received within: "
+ + timeoutMillis + " ms.");
+ }
+ synchronized (mLock) {
+ if (mEventQueue.isEmpty()) {
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
}
}
- } finally {
+ }
+ } finally {
+ synchronized (mLock) {
mWaitingForEventDelivery = false;
mEventQueue.clear();
mLock.notifyAll();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c559943..540678e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -411,6 +411,14 @@
public static final int NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED = 3;
/**
+ * Default and maximum timeout in milliseconds after which unlocking with weak auth times out,
+ * i.e. the user has to use a strong authentication method like password, PIN or pattern.
+ *
+ * @hide
+ */
+ public static final long DEFAULT_STRONG_AUTH_TIMEOUT_MS = 72 * 60 * 60 * 1000; // 72h
+
+ /**
* A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that
* allows a mobile device management application or NFC programmer application which starts
* managed provisioning to pass data to the management application instance after provisioning.
@@ -2330,6 +2338,78 @@
}
/**
+ * Called by a device/profile owner to set the timeout after which unlocking with secondary, non
+ * strong auth (e.g. fingerprint, trust agents) times out, i.e. the user has to use a strong
+ * authentication method like password, pin or pattern.
+ *
+ * <p>This timeout is used internally to reset the timer to require strong auth again after
+ * specified timeout each time it has been successfully used.
+ *
+ * <p>Fingerprint can also be disabled altogether using {@link #KEYGUARD_DISABLE_FINGERPRINT}.
+ *
+ * <p>Trust agents can also be disabled altogether using {@link #KEYGUARD_DISABLE_TRUST_AGENTS}.
+ *
+ * <p>The calling device admin must be a device or profile owner. If it is not,
+ * a {@link SecurityException} will be thrown.
+ *
+ * <p>This method can be called on the {@link DevicePolicyManager} instance returned by
+ * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
+ * profile.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param timeoutMs The new timeout, after which the user will have to unlock with strong
+ * authentication method. If the timeout is lower than 1 hour (minimum) or higher than
+ * 72 hours (default and maximum) an {@link IllegalArgumentException} is thrown.
+ *
+ * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws IllegalArgumentException if the timeout is lower than 1 hour (minimum) or higher than
+ * 72 hours (default and maximum)
+ *
+ * @hide
+ */
+ public void setRequiredStrongAuthTimeout(@NonNull ComponentName admin,
+ long timeoutMs) {
+ if (mService != null) {
+ try {
+ mService.setRequiredStrongAuthTimeout(admin, timeoutMs, mParentInstance);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Determine for how long the user will be able to use secondary, non strong auth for
+ * authentication, since last strong method authentication (password, pin or pattern) was used.
+ * After the returned timeout the user is required to use strong authentication method.
+ *
+ * <p>This method can be called on the {@link DevicePolicyManager} instance
+ * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
+ * restrictions on the parent profile.
+ *
+ * @param admin The name of the admin component to check, or {@code null} to aggregate
+ * accross all participating admins.
+ * @return The timeout or default timeout if not configured
+ *
+ * @hide
+ */
+ public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin) {
+ return getRequiredStrongAuthTimeout(admin, myUserId());
+ }
+
+ /** @hide per-user version */
+ public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin, @UserIdInt int userId) {
+ if (mService != null) {
+ try {
+ return mService.getRequiredStrongAuthTimeout(admin, userId, mParentInstance);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return DEFAULT_STRONG_AUTH_TIMEOUT_MS;
+ }
+
+ /**
* Make the device lock immediately, as if the lock screen timeout has expired at the point of
* this call.
* <p>
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index f428c77..8c376bb 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -82,6 +82,9 @@
long getMaximumTimeToLock(in ComponentName who, int userHandle, boolean parent);
long getMaximumTimeToLockForUserAndProfiles(int userHandle);
+ void setRequiredStrongAuthTimeout(in ComponentName who, long timeMs, boolean parent);
+ long getRequiredStrongAuthTimeout(in ComponentName who, int userId, boolean parent);
+
void lockNow(boolean parent);
void wipeData(int flags);
diff --git a/core/java/android/app/package.html b/core/java/android/app/package.html
index f37f1dc..b259cad 100644
--- a/core/java/android/app/package.html
+++ b/core/java/android/app/package.html
@@ -34,7 +34,7 @@
<p>For information about using some the classes in this package, see the following
documents: <a href="{@docRoot}guide/topics/fundamentals/activities.html">Activities</a>, <a
href="{@docRoot}guide/topics/fundamentals/services.html">Services</a>, <a
-href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a>, <a
+href="{@docRoot}guide/components/fragments.html">Fragments</a>, <a
href="{@docRoot}guide/topics/ui/actionbar.html">Using the Action Bar</a>, <a
href="{@docRoot}guide/topics/ui/dialogs.html">Creating Dialogs</a>, and <a
href="{@docRoot}guide/topics/ui/notifiers/index.html">Notifying the User</a>.</p>
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 22ab43b..d07b5457 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -265,6 +265,7 @@
public static final String SCHEME_HTTPS = "https";
private int mPriority;
+ private int mOrder;
private final ArrayList<String> mActions;
private ArrayList<String> mCategories = null;
private ArrayList<String> mDataSchemes = null;
@@ -425,6 +426,7 @@
*/
public IntentFilter(IntentFilter o) {
mPriority = o.mPriority;
+ mOrder = o.mOrder;
mActions = new ArrayList<String>(o.mActions);
if (o.mCategories != null) {
mCategories = new ArrayList<String>(o.mCategories);
@@ -477,6 +479,16 @@
return mPriority;
}
+ /** @hide */
+ public final void setOrder(int order) {
+ mOrder = order;
+ }
+
+ /** @hide */
+ public final int getOrder() {
+ return mOrder;
+ }
+
/**
* Set whether this filter will needs to be automatically verified against its data URIs or not.
* The default is false.
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 94c406a..5d2c6cd 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -377,6 +377,7 @@
/** @hide */
@IntDef({
+ SCREEN_ORIENTATION_UNSET,
SCREEN_ORIENTATION_UNSPECIFIED,
SCREEN_ORIENTATION_LANDSCAPE,
SCREEN_ORIENTATION_PORTRAIT,
@@ -398,6 +399,15 @@
public @interface ScreenOrientation {}
/**
+ * Internal constant used to indicate that the app didn't set a specific orientation value.
+ * Different from {@link #SCREEN_ORIENTATION_UNSPECIFIED} below as the app can set its
+ * orientation to {@link #SCREEN_ORIENTATION_UNSPECIFIED} while this means that the app didn't
+ * set anything. The system will mostly treat this similar to
+ * {@link #SCREEN_ORIENTATION_UNSPECIFIED}.
+ * @hide
+ */
+ public static final int SCREEN_ORIENTATION_UNSET = -2;
+ /**
* Constant corresponding to <code>unspecified</code> in
* the {@link android.R.attr#screenOrientation} attribute.
*/
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index a110383..aea843a 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -390,15 +390,17 @@
@VisibleForTesting
protected boolean inSystemImage(int callerUid) {
String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
- for (String name : packages) {
- try {
- PackageInfo packageInfo =
- mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
- if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return true;
+ if (packages != null) {
+ for (String name : packages) {
+ try {
+ PackageInfo packageInfo =
+ mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
+ if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
}
- } catch (PackageManager.NameNotFoundException e) {
- return false;
}
}
return false;
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 4b21187..1d85493 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -221,34 +221,32 @@
public abstract void tearDown(@NonNull Surface surface) throws CameraAccessException;
/**
- * <p>
- * Finish the deferred output configurations where the output Surface was not configured before.
- * </p>
- * <p>
- * For camera use cases where a preview and other output configurations need to be configured,
- * it can take some time for the preview Surface to be ready (e.g., if the preview Surface is
- * obtained from {@link android.view.SurfaceView}, the SurfaceView is ready after the UI layout
- * is done, then it takes some time to get the preview Surface).
- * </p>
- * <p>
- * To speed up camera startup time, the application can configure the
- * {@link CameraCaptureSession} with the desired preview size, and defer the preview output
- * configuration until the Surface is ready. After the {@link CameraCaptureSession} is created
- * successfully with this deferred configuration and other normal configurations, the
- * application can submit requests that don't include deferred output Surfaces. Once the
- * deferred Surface is ready, the application can set the Surface to the same deferred output
- * configuration with the {@link OutputConfiguration#setDeferredSurface} method, and then finish
- * the deferred output configuration via this method, before it can submit requests with this
- * output target.
- * </p>
- * <p>
- * The output Surfaces included by this list of deferred {@link OutputConfiguration
- * OutputConfigurations} can be used as {@link CaptureRequest} targets as soon as this call
- * returns;
- * </p>
- * <p>
- * This method is not supported by Legacy devices.
- * </p>
+ * <p>Finish the deferred output configurations where the output Surface was not configured
+ * before.</p>
+ *
+ * <p>For camera use cases where a preview and other output configurations need to be
+ * configured, it can take some time for the preview Surface to be ready. For example, if the
+ * preview Surface is obtained from {@link android.view.SurfaceView}, the SurfaceView will only
+ * be ready after the UI layout is done, potentially delaying camera startup.</p>
+ *
+ * <p>To speed up camera startup time, the application can configure the
+ * {@link CameraCaptureSession} with the eventual preview size (via
+ * {@link OutputConfiguration#OutputConfiguration(Size,Class) a deferred OutputConfiguration}),
+ * and defer the preview output configuration until the Surface is ready. After the
+ * {@link CameraCaptureSession} is created successfully with this deferred output and other
+ * normal outputs, the application can start submitting requests as long as they do not include
+ * deferred output Surfaces. Once a deferred Surface is ready, the application can set the
+ * Surface on the deferred output configuration with the
+ * {@link OutputConfiguration#setDeferredSurface} method, and then finish the deferred output
+ * configuration via this method, before it can submit capture requests with this output
+ * target.</p>
+ *
+ * <p>The output Surfaces included by this list of deferred
+ * {@link OutputConfiguration OutputConfigurations} can be used as {@link CaptureRequest}
+ * targets as soon as this call returns.</p>
+ *
+ * <p>This method is not supported by
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}-level devices.</p>
*
* @param deferredOutputConfigs a list of {@link OutputConfiguration OutputConfigurations} that
* have had {@link OutputConfiguration#setDeferredSurface setDeferredSurface} invoked
@@ -256,13 +254,12 @@
* @throws CameraAccessException if the camera device is no longer connected or has encountered
* a fatal error.
* @throws IllegalStateException if this session is no longer active, either because the session
- * was explicitly closed, a new session has been created or the camera device has
- * been closed. Or if this output configuration was already finished with the
- * included surface before.
+ * was explicitly closed, a new session has been created, or the camera device has
+ * been closed.
* @throws IllegalArgumentException for invalid output configurations, including ones where the
* source of the Surface is no longer valid or the Surface is from a unsupported
- * source.
- * @hide
+ * source. Or if one of the output configuration was already finished with an
+ * included surface in a prior call.
*/
public abstract void finishDeferredConfiguration(
List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException;
@@ -764,13 +761,6 @@
}
/**
- * Temporary for migrating to Callback naming
- * @hide
- */
- public static abstract class StateListener extends StateCallback {
- }
-
- /**
* <p>A callback object for tracking the progress of a {@link CaptureRequest} submitted to the
* camera device.</p>
*
@@ -835,16 +825,6 @@
*/
public void onCaptureStarted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request, long timestamp, long frameNumber) {
- // Temporary trampoline for API change transition
- onCaptureStarted(session, request, timestamp);
- }
-
- /**
- * Temporary for API change transition
- * @hide
- */
- public void onCaptureStarted(CameraCaptureSession session,
- CaptureRequest request, long timestamp) {
// default empty implementation
}
@@ -1064,11 +1044,4 @@
}
}
- /**
- * Temporary for migrating to Callback naming
- * @hide
- */
- public static abstract class CaptureListener extends CaptureCallback {
- }
-
}
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index c54d1e1..9e00b65 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -239,9 +239,9 @@
* <p>If a prior CameraCaptureSession already exists when this method is called, the previous
* session will no longer be able to accept new capture requests and will be closed. Any
* in-progress capture requests made on the prior session will be completed before it's closed.
- * {@link CameraCaptureSession.StateListener#onConfigured} for the new session may be invoked
- * before {@link CameraCaptureSession.StateListener#onClosed} is invoked for the prior
- * session. Once the new session is {@link CameraCaptureSession.StateListener#onConfigured
+ * {@link CameraCaptureSession.StateCallback#onConfigured} for the new session may be invoked
+ * before {@link CameraCaptureSession.StateCallback#onClosed} is invoked for the prior
+ * session. Once the new session is {@link CameraCaptureSession.StateCallback#onConfigured
* configured}, it is able to start capturing its own requests. To minimize the transition time,
* the {@link CameraCaptureSession#abortCaptures} call can be used to discard the remaining
* requests for the prior capture session before a new one is created. Note that once the new
@@ -265,7 +265,7 @@
* but the camera device won't meet the frame rate guarantees as described in
* {@link StreamConfigurationMap#getOutputMinFrameDuration}. Or third, if the output set
* cannot be used at all, session creation will fail entirely, with
- * {@link CameraCaptureSession.StateListener#onConfigureFailed} being invoked.</p>
+ * {@link CameraCaptureSession.StateCallback#onConfigureFailed} being invoked.</p>
*
* <p>For the type column, {@code PRIV} refers to any target whose available sizes are found
* using {@link StreamConfigurationMap#getOutputSizes(Class)} with no direct application-visible
@@ -984,13 +984,6 @@
}
/**
- * Temporary for migrating to Callback naming
- * @hide
- */
- public static abstract class StateListener extends StateCallback {
- }
-
- /**
* To be inherited by android.hardware.camera2.* code only.
* @hide
*/
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 69c00e9..f897d85 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -229,7 +229,6 @@
* @param klass a non-{@code null} {@link Class} object reference that indicates the source of
* this surface. Only {@link android.view.SurfaceHolder SurfaceHolder.class} and
* {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported.
- * @hide
*/
public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass) {
checkNotNull(klass, "surfaceSize must not be null");
@@ -283,7 +282,6 @@
* @throws IllegalArgumentException if the Surface is invalid.
* @throws IllegalStateException if a Surface was already set to this deferred
* OutputConfiguration.
- * @hide
*/
public void setDeferredSurface(@NonNull Surface surface) {
checkNotNull(surface, "Surface must not be null");
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index 6f39935..5a79926 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -278,7 +278,6 @@
protected void finalize() throws Throwable {
try {
mCloseGuard.warnIfOpen();
- close();
} finally {
super.finalize();
}
diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java
index 6129119e..b531e5f 100644
--- a/core/java/android/hardware/usb/UsbRequest.java
+++ b/core/java/android/hardware/usb/UsbRequest.java
@@ -150,6 +150,11 @@
*/
public boolean queue(ByteBuffer buffer, int length) {
boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
+
+ // save our buffer for when the request has completed
+ mBuffer = buffer;
+ mLength = length;
+
boolean result;
if (buffer.isDirect()) {
result = native_queue_direct(buffer, length, out);
@@ -158,10 +163,9 @@
} else {
throw new IllegalArgumentException("buffer is not direct and has no array");
}
- if (result) {
- // save our buffer for when the request has completed
- mBuffer = buffer;
- mLength = length;
+ if (!result) {
+ mBuffer = null;
+ mLength = 0;
}
return result;
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 7669635..fd9e60d 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -343,6 +343,15 @@
public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED";
/**
+ * Action used to display a dialog that asks the user whether to avoid a network that is no
+ * longer validated. This intent is used to start the dialog in settings via startActivity.
+ *
+ * @hide
+ */
+ public static final String ACTION_PROMPT_LOST_VALIDATION =
+ "android.net.conn.PROMPT_LOST_VALIDATION";
+
+ /**
* Invalid tethering type.
* @see #startTethering(int, OnStartTetheringCallback, boolean)
* @hide
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index ebb9601..6196400 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -182,8 +182,15 @@
*/
public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17;
+ /**
+ * Indicates that this network is available for use by apps, and not a network that is being
+ * kept up in the background to facilitate fast network switching.
+ * @hide
+ */
+ public static final int NET_CAPABILITY_FOREGROUND = 18;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_CAPTIVE_PORTAL;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_FOREGROUND;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -194,7 +201,8 @@
// http://b/18206275
(1 << NET_CAPABILITY_TRUSTED) |
(1 << NET_CAPABILITY_VALIDATED) |
- (1 << NET_CAPABILITY_CAPTIVE_PORTAL);
+ (1 << NET_CAPABILITY_CAPTIVE_PORTAL) |
+ (1 << NET_CAPABILITY_FOREGROUND);
/**
* Network specifier for factories which want to match any network specifier
@@ -217,8 +225,7 @@
* get immediately torn down because they do not have the requested capability.
*/
private static final long NON_REQUESTABLE_CAPABILITIES =
- (1 << NET_CAPABILITY_VALIDATED) |
- (1 << NET_CAPABILITY_CAPTIVE_PORTAL);
+ MUTABLE_CAPABILITIES & ~(1 << NET_CAPABILITY_TRUSTED);
/**
* Capabilities that are set by default when the object is constructed.
@@ -325,6 +332,7 @@
public String describeFirstNonRequestableCapability() {
if (hasCapability(NET_CAPABILITY_VALIDATED)) return "NET_CAPABILITY_VALIDATED";
if (hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return "NET_CAPABILITY_CAPTIVE_PORTAL";
+ if (hasCapability(NET_CAPABILITY_FOREGROUND)) return "NET_CAPABILITY_FOREGROUND";
// This cannot happen unless the preceding checks are incomplete.
if ((mNetworkCapabilities & NON_REQUESTABLE_CAPABILITIES) != 0) {
return "unknown non-requestable capabilities " + Long.toHexString(mNetworkCapabilities);
@@ -352,6 +360,11 @@
(that.mNetworkCapabilities & ~MUTABLE_CAPABILITIES));
}
+ private boolean equalsNetCapabilitiesRequestable(NetworkCapabilities that) {
+ return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) ==
+ (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES));
+ }
+
/**
* Removes the NET_CAPABILITY_NOT_RESTRICTED capability if all the capabilities it provides are
* typically provided by restricted networks.
@@ -756,6 +769,19 @@
equalsSpecifier(nc));
}
+ /**
+ * Checks that our requestable capabilities are the same as those of the given
+ * {@code NetworkCapabilities}.
+ *
+ * @hide
+ */
+ public boolean equalRequestableCapabilities(NetworkCapabilities nc) {
+ if (nc == null) return false;
+ return (equalsNetCapabilitiesRequestable(nc) &&
+ equalsTransportTypes(nc) &&
+ equalsSpecifier(nc));
+ }
+
@Override
public boolean equals(Object obj) {
if (obj == null || (obj instanceof NetworkCapabilities == false)) return false;
@@ -840,6 +866,7 @@
case NET_CAPABILITY_NOT_VPN: capabilities += "NOT_VPN"; break;
case NET_CAPABILITY_VALIDATED: capabilities += "VALIDATED"; break;
case NET_CAPABILITY_CAPTIVE_PORTAL: capabilities += "CAPTIVE_PORTAL"; break;
+ case NET_CAPABILITY_FOREGROUND: capabilities += "FOREGROUND"; break;
}
if (++i < types.length) capabilities += "&";
}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 4501f7b..ae72470 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -49,7 +49,7 @@
public final int legacyType;
/**
- * A NetworkRequest as used by the system can be one of three types:
+ * A NetworkRequest as used by the system can be one of the following types:
*
* - LISTEN, for which the framework will issue callbacks about any
* and all networks that match the specified NetworkCapabilities,
@@ -64,7 +64,20 @@
* current network (if any) that matches the capabilities of the
* default Internet request (mDefaultRequest), but which cannot cause
* the framework to either create or retain the existence of any
- * specific network.
+ * specific network. Note that from the point of view of the request
+ * matching code, TRACK_DEFAULT is identical to REQUEST: its special
+ * behaviour is not due to different semantics, but to the fact that
+ * the system will only ever create a TRACK_DEFAULT with capabilities
+ * that are identical to the default request's capabilities, thus
+ * causing it to share fate in every way with the default request.
+ *
+ * - BACKGROUND_REQUEST, like REQUEST but does not cause any networks
+ * to retain the NET_CAPABILITY_FOREGROUND capability. A network with
+ * no foreground requests is in the background. A network that has
+ * one or more background requests and loses its last foreground
+ * request to a higher-scoring network will not go into the
+ * background immediately, but will linger and go into the background
+ * after the linger timeout.
*
* - The value NONE is used only by applications. When an application
* creates a NetworkRequest, it does not have a type; the type is set
@@ -77,7 +90,8 @@
NONE,
LISTEN,
TRACK_DEFAULT,
- REQUEST
+ REQUEST,
+ BACKGROUND_REQUEST,
};
/**
@@ -140,7 +154,7 @@
* Add the given capability requirement to this builder. These represent
* the requested network's required capabilities. Note that when searching
* for a network to satisfy a request, all capabilities requested must be
- * satisfied. See {@link NetworkCapabilities} for {@code NET_CAPABILITIY_*}
+ * satisfied. See {@link NetworkCapabilities} for {@code NET_CAPABILITY_*}
* definitions.
*
* @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to add.
@@ -284,7 +298,7 @@
};
/**
- * Returns true iff. the contained NetworkRequest is of type LISTEN.
+ * Returns true iff. this NetworkRequest is of type LISTEN.
*
* @hide
*/
@@ -298,8 +312,9 @@
* - should be associated with at most one satisfying network
* at a time;
*
- * - should cause a network to be kept up if it is the best network
- * which can satisfy the NetworkRequest.
+ * - should cause a network to be kept up, but not necessarily in
+ * the foreground, if it is the best network which can satisfy the
+ * NetworkRequest.
*
* For full detail of how isRequest() is used for pairing Networks with
* NetworkRequests read rematchNetworkAndRequests().
@@ -307,9 +322,36 @@
* @hide
*/
public boolean isRequest() {
+ return isForegroundRequest() || isBackgroundRequest();
+ }
+
+ /**
+ * Returns true iff. the contained NetworkRequest is one that:
+ *
+ * - should be associated with at most one satisfying network
+ * at a time;
+ *
+ * - should cause a network to be kept up and in the foreground if
+ * it is the best network which can satisfy the NetworkRequest.
+ *
+ * For full detail of how isRequest() is used for pairing Networks with
+ * NetworkRequests read rematchNetworkAndRequests().
+ *
+ * @hide
+ */
+ public boolean isForegroundRequest() {
return type == Type.TRACK_DEFAULT || type == Type.REQUEST;
}
+ /**
+ * Returns true iff. this NetworkRequest is of type BACKGROUND_REQUEST.
+ *
+ * @hide
+ */
+ public boolean isBackgroundRequest() {
+ return type == Type.BACKGROUND_REQUEST;
+ }
+
public String toString() {
return "NetworkRequest [ " + type + " id=" + requestId +
(legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") +
diff --git a/core/java/android/os/PatternMatcher.java b/core/java/android/os/PatternMatcher.java
index 56dc837..3890fbf 100644
--- a/core/java/android/os/PatternMatcher.java
+++ b/core/java/android/os/PatternMatcher.java
@@ -16,6 +16,10 @@
package android.os;
+import android.util.Log;
+
+import java.util.Arrays;
+
/**
* A simple pattern matcher, which is safe to use on untrusted data: it does
* not provide full reg-exp support, only simple globbing that can not be
@@ -44,13 +48,59 @@
* wildcard part of a normal regexp.
*/
public static final int PATTERN_SIMPLE_GLOB = 2;
-
+
+ /**
+ * Pattern type: the given pattern is interpreted with a regular
+ * expression-like syntax for matching against the string it is tested
+ * against. Supported tokens include dot ({@code .}) and sets ({@code [...]})
+ * with full support for character ranges and the not ({@code ^}) modifier.
+ * Supported modifiers include star ({@code *}) for zero-or-more, plus ({@code +})
+ * for one-or-more and full range ({@code {...}}) support. This is a simple
+ * evaulation implementation in which matching is done against the pattern in
+ * realtime with no backtracking support.
+ *
+ * {@hide} Pending approval for public API
+ */
+ public static final int PATTERN_ADVANCED_GLOB = 3;
+
+ // token types for advanced matching
+ private static final int TOKEN_TYPE_LITERAL = 0;
+ private static final int TOKEN_TYPE_ANY = 1;
+ private static final int TOKEN_TYPE_SET = 2;
+ private static final int TOKEN_TYPE_INVERSE_SET = 3;
+
+ // Return for no match
+ private static final int NO_MATCH = -1;
+
+ private static final String TAG = "PatternMatcher";
+
+ // Parsed placeholders for advanced patterns
+ private static final int PARSED_TOKEN_CHAR_SET_START = -1;
+ private static final int PARSED_TOKEN_CHAR_SET_INVERSE_START = -2;
+ private static final int PARSED_TOKEN_CHAR_SET_STOP = -3;
+ private static final int PARSED_TOKEN_CHAR_ANY = -4;
+ private static final int PARSED_MODIFIER_RANGE_START = -5;
+ private static final int PARSED_MODIFIER_RANGE_STOP = -6;
+ private static final int PARSED_MODIFIER_ZERO_OR_MORE = -7;
+ private static final int PARSED_MODIFIER_ONE_OR_MORE = -8;
+
private final String mPattern;
private final int mType;
-
+ private final int[] mParsedPattern;
+
+
+ private static final int MAX_PATTERN_STORAGE = 2048;
+ // workspace to use for building a parsed advanced pattern;
+ private static final int[] sParsedPatternScratch = new int[MAX_PATTERN_STORAGE];
+
public PatternMatcher(String pattern, int type) {
mPattern = pattern;
mType = type;
+ if (mType == PATTERN_ADVANCED_GLOB) {
+ mParsedPattern = parseAndVerifyAdvancedPattern(pattern);
+ } else {
+ mParsedPattern = null;
+ }
}
public final String getPath() {
@@ -62,7 +112,7 @@
}
public boolean match(String str) {
- return matchPattern(mPattern, str, mType);
+ return matchPattern(str, mPattern, mParsedPattern, mType);
}
public String toString() {
@@ -77,6 +127,9 @@
case PATTERN_SIMPLE_GLOB:
type = "GLOB: ";
break;
+ case PATTERN_ADVANCED_GLOB:
+ type = "ADVANCED: ";
+ break;
}
return "PatternMatcher{" + type + mPattern + "}";
}
@@ -88,11 +141,13 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mPattern);
dest.writeInt(mType);
+ dest.writeIntArray(mParsedPattern);
}
public PatternMatcher(Parcel src) {
mPattern = src.readString();
mType = src.readInt();
+ mParsedPattern = src.createIntArray();
}
public static final Parcelable.Creator<PatternMatcher> CREATOR
@@ -106,16 +161,21 @@
}
};
- static boolean matchPattern(String pattern, String match, int type) {
+ static boolean matchPattern(String match, String pattern, int[] parsedPattern, int type) {
if (match == null) return false;
if (type == PATTERN_LITERAL) {
return pattern.equals(match);
} if (type == PATTERN_PREFIX) {
return match.startsWith(pattern);
- } else if (type != PATTERN_SIMPLE_GLOB) {
- return false;
+ } else if (type == PATTERN_SIMPLE_GLOB) {
+ return matchGlobPattern(pattern, match);
+ } else if (type == PATTERN_ADVANCED_GLOB) {
+ return matchAdvancedPattern(parsedPattern, match);
}
-
+ return false;
+ }
+
+ static boolean matchGlobPattern(String pattern, String match) {
final int NP = pattern.length();
if (NP <= 0) {
return match.length() <= 0;
@@ -194,4 +254,310 @@
return false;
}
-}
+
+ /**
+ * Parses the advanced pattern and returns an integer array representation of it. The integer
+ * array treats each field as a character if positive and a unique token placeholder if
+ * negative. This method will throw on any pattern structure violations.
+ */
+ synchronized static int[] parseAndVerifyAdvancedPattern(String pattern) {
+ int ip = 0;
+ final int LP = pattern.length();
+
+ int it = 0;
+
+ boolean inSet = false;
+ boolean inRange = false;
+ boolean inCharClass = false;
+
+ boolean addToParsedPattern;
+
+ while (ip < LP) {
+ if (it > MAX_PATTERN_STORAGE - 3) {
+ throw new IllegalArgumentException("Pattern is too large!");
+ }
+
+ char c = pattern.charAt(ip);
+ addToParsedPattern = false;
+
+ switch (c) {
+ case '[':
+ if (inSet) {
+ addToParsedPattern = true; // treat as literal or char class in set
+ } else {
+ if (pattern.charAt(ip + 1) == '^') {
+ sParsedPatternScratch[it++] = PARSED_TOKEN_CHAR_SET_INVERSE_START;
+ ip++; // skip over the '^'
+ } else {
+ sParsedPatternScratch[it++] = PARSED_TOKEN_CHAR_SET_START;
+ }
+ ip++; // move to the next pattern char
+ inSet = true;
+ continue;
+ }
+ break;
+ case ']':
+ if (!inSet) {
+ addToParsedPattern = true; // treat as literal outside of set
+ } else {
+ int parsedToken = sParsedPatternScratch[it - 1];
+ if (parsedToken == PARSED_TOKEN_CHAR_SET_START ||
+ parsedToken == PARSED_TOKEN_CHAR_SET_INVERSE_START) {
+ throw new IllegalArgumentException(
+ "You must define characters in a set.");
+ }
+ sParsedPatternScratch[it++] = PARSED_TOKEN_CHAR_SET_STOP;
+ inSet = false;
+ inCharClass = false;
+ }
+ break;
+ case '{':
+ if (!inSet) {
+ if (it == 0 || isParsedModifier(sParsedPatternScratch[it - 1])) {
+ throw new IllegalArgumentException("Modifier must follow a token.");
+ }
+ sParsedPatternScratch[it++] = PARSED_MODIFIER_RANGE_START;
+ ip++;
+ inRange = true;
+ }
+ break;
+ case '}':
+ if (inRange) { // only terminate the range if we're currently in one
+ sParsedPatternScratch[it++] = PARSED_MODIFIER_RANGE_STOP;
+ inRange = false;
+ }
+ break;
+ case '*':
+ if (!inSet) {
+ if (it == 0 || isParsedModifier(sParsedPatternScratch[it - 1])) {
+ throw new IllegalArgumentException("Modifier must follow a token.");
+ }
+ sParsedPatternScratch[it++] = PARSED_MODIFIER_ZERO_OR_MORE;
+ }
+ break;
+ case '+':
+ if (!inSet) {
+ if (it == 0 || isParsedModifier(sParsedPatternScratch[it - 1])) {
+ throw new IllegalArgumentException("Modifier must follow a token.");
+ }
+ sParsedPatternScratch[it++] = PARSED_MODIFIER_ONE_OR_MORE;
+ }
+ break;
+ case '.':
+ if (!inSet) {
+ sParsedPatternScratch[it++] = PARSED_TOKEN_CHAR_ANY;
+ }
+ break;
+ case '\\': // escape
+ if (ip + 1 >= LP) {
+ throw new IllegalArgumentException("Escape found at end of pattern!");
+ }
+ c = pattern.charAt(++ip);
+ addToParsedPattern = true;
+ break;
+ default:
+ addToParsedPattern = true;
+ break;
+ }
+ if (inSet) {
+ if (inCharClass) {
+ sParsedPatternScratch[it++] = c;
+ inCharClass = false;
+ } else {
+ // look forward for character class
+ if (ip + 2 < LP
+ && pattern.charAt(ip + 1) == '-'
+ && pattern.charAt(ip + 2) != ']') {
+ inCharClass = true;
+ sParsedPatternScratch[it++] = c; // set first token as lower end of range
+ ip++; // advance past dash
+ } else { // literal
+ sParsedPatternScratch[it++] = c; // set first token as literal
+ sParsedPatternScratch[it++] = c; // set second set as literal
+ }
+ }
+ } else if (inRange) {
+ int endOfSet = pattern.indexOf('}', ip);
+ if (endOfSet < 0) {
+ throw new IllegalArgumentException("Range not ended with '}'");
+ }
+ String rangeString = pattern.substring(ip, endOfSet);
+ int commaIndex = rangeString.indexOf(',');
+ try {
+ final int rangeMin;
+ final int rangeMax;
+ if (commaIndex < 0) {
+ int parsedRange = Integer.parseInt(rangeString);
+ rangeMin = rangeMax = parsedRange;
+ } else {
+ rangeMin = Integer.parseInt(rangeString.substring(0, commaIndex));
+ if (commaIndex == rangeString.length() - 1) { // e.g. {n,} (n or more)
+ rangeMax = Integer.MAX_VALUE;
+ } else {
+ rangeMax = Integer.parseInt(rangeString.substring(commaIndex + 1));
+ }
+ }
+ if (rangeMin > rangeMax) {
+ throw new IllegalArgumentException(
+ "Range quantifier minimum is greater than maximum");
+ }
+ sParsedPatternScratch[it++] = rangeMin;
+ sParsedPatternScratch[it++] = rangeMax;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Range number format incorrect", e);
+ }
+ ip = endOfSet;
+ continue; // don't increment ip
+ } else if (addToParsedPattern) {
+ sParsedPatternScratch[it++] = c;
+ }
+ ip++;
+ }
+ if (inSet) {
+ throw new IllegalArgumentException("Set was not terminated!");
+ }
+ return Arrays.copyOf(sParsedPatternScratch, it);
+ }
+
+ private static boolean isParsedModifier(int parsedChar) {
+ return parsedChar == PARSED_MODIFIER_ONE_OR_MORE ||
+ parsedChar == PARSED_MODIFIER_ZERO_OR_MORE ||
+ parsedChar == PARSED_MODIFIER_RANGE_STOP ||
+ parsedChar == PARSED_MODIFIER_RANGE_START;
+ }
+
+ static boolean matchAdvancedPattern(int[] parsedPattern, String match) {
+
+ // create indexes
+ int ip = 0, im = 0;
+
+ // one-time length check
+ final int LP = parsedPattern.length, LM = match.length();
+
+ // The current character being analyzed in the pattern
+ int patternChar;
+
+ int tokenType;
+
+ int charSetStart = 0, charSetEnd = 0;
+
+ while (ip < LP) { // we still have content in the pattern
+
+ patternChar = parsedPattern[ip];
+ // get the match type of the next verb
+
+ switch (patternChar) {
+ case PARSED_TOKEN_CHAR_ANY:
+ tokenType = TOKEN_TYPE_ANY;
+ ip++;
+ break;
+ case PARSED_TOKEN_CHAR_SET_START:
+ case PARSED_TOKEN_CHAR_SET_INVERSE_START:
+ tokenType = patternChar == PARSED_TOKEN_CHAR_SET_START
+ ? TOKEN_TYPE_SET
+ : TOKEN_TYPE_INVERSE_SET;
+ charSetStart = ip + 1; // start from the char after the set start
+ while (++ip < LP && parsedPattern[ip] != PARSED_TOKEN_CHAR_SET_STOP);
+ charSetEnd = ip - 1; // we're on the set stop, end is the previous
+ ip++; // move the pointer to the next pattern entry
+ break;
+ default:
+ charSetStart = ip;
+ tokenType = TOKEN_TYPE_LITERAL;
+ ip++;
+ break;
+ }
+
+ final int minRepetition;
+ final int maxRepetition;
+
+ // look for a match length modifier
+ if (ip >= LP) {
+ minRepetition = maxRepetition = 1;
+ } else {
+ patternChar = parsedPattern[ip];
+ switch (patternChar) {
+ case PARSED_MODIFIER_ZERO_OR_MORE:
+ minRepetition = 0;
+ maxRepetition = Integer.MAX_VALUE;
+ ip++;
+ break;
+ case PARSED_MODIFIER_ONE_OR_MORE:
+ minRepetition = 1;
+ maxRepetition = Integer.MAX_VALUE;
+ ip++;
+ break;
+ case PARSED_MODIFIER_RANGE_START:
+ minRepetition = parsedPattern[++ip];
+ maxRepetition = parsedPattern[++ip];
+ ip += 2; // step over PARSED_MODIFIER_RANGE_STOP and on to the next token
+ break;
+ default:
+ minRepetition = maxRepetition = 1; // implied literal
+ break;
+ }
+ }
+ if (minRepetition > maxRepetition) {
+ return false;
+ }
+
+ // attempt to match as many characters as possible
+ int matched = matchChars(match, im, LM, tokenType, minRepetition, maxRepetition,
+ parsedPattern, charSetStart, charSetEnd);
+
+ // if we found a conflict, return false immediately
+ if (matched == NO_MATCH) {
+ return false;
+ }
+
+ // move the match pointer the number of characters matched
+ im += matched;
+ }
+ return ip >= LP && im >= LM; // have parsed entire string and regex
+ }
+
+ private static int matchChars(String match, int im, final int lm, int tokenType,
+ int minRepetition, int maxRepetition, int[] parsedPattern,
+ int tokenStart, int tokenEnd) {
+ int matched = 0;
+
+ while(matched < maxRepetition
+ && matchChar(match, im + matched, lm, tokenType, parsedPattern, tokenStart,
+ tokenEnd)) {
+ matched++;
+ }
+
+ return matched < minRepetition ? NO_MATCH : matched;
+ }
+
+ private static boolean matchChar(String match, int im, final int lm, int tokenType,
+ int[] parsedPattern, int tokenStart, int tokenEnd) {
+ if (im >= lm) { // we've overrun the string, no match
+ return false;
+ }
+ switch (tokenType) {
+ case TOKEN_TYPE_ANY:
+ return true;
+ case TOKEN_TYPE_SET:
+ for (int i = tokenStart; i < tokenEnd; i += 2) {
+ char matchChar = match.charAt(im);
+ if (matchChar >= parsedPattern[i] && matchChar <= parsedPattern[i + 1]) {
+ return true;
+ }
+ }
+ return false;
+ case TOKEN_TYPE_INVERSE_SET:
+ for (int i = tokenStart; i < tokenEnd; i += 2) {
+ char matchChar = match.charAt(im);
+ if (matchChar >= parsedPattern[i] && matchChar <= parsedPattern[i + 1]) {
+ return false;
+ }
+ }
+ return true;
+ case TOKEN_TYPE_LITERAL:
+ return match.charAt(im) == parsedPattern[tokenStart];
+ default:
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 507379b..90bd11f 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -724,6 +724,7 @@
String line = null;
int bytesWrittenInMiB = -1, bytesStashedInMiB = -1;
int timeTotal = -1;
+ int uncryptTime = -1;
int sourceVersion = -1;
while ((line = in.readLine()) != null) {
// Here is an example of lines in last_install:
@@ -759,6 +760,8 @@
if (line.startsWith("time")) {
timeTotal = scaled;
+ } else if (line.startsWith("uncrypt_time")) {
+ uncryptTime = scaled;
} else if (line.startsWith("source_build")) {
sourceVersion = scaled;
} else if (line.startsWith("bytes_written")) {
@@ -774,6 +777,9 @@
if (timeTotal != -1) {
MetricsLogger.histogram(context, "ota_time_total", timeTotal);
}
+ if (uncryptTime != -1) {
+ MetricsLogger.histogram(context, "ota_uncrypt_time", uncryptTime);
+ }
if (sourceVersion != -1) {
MetricsLogger.histogram(context, "ota_source_version", sourceVersion);
}
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index 5849350..3546e17 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -288,20 +288,22 @@
* @see #beginBroadcast
*/
public void finishBroadcast() {
- if (mBroadcastCount < 0) {
- throw new IllegalStateException(
- "finishBroadcast() called outside of a broadcast");
- }
-
- Object[] active = mActiveBroadcast;
- if (active != null) {
- final int N = mBroadcastCount;
- for (int i=0; i<N; i++) {
- active[i] = null;
+ synchronized (mCallbacks) {
+ if (mBroadcastCount < 0) {
+ throw new IllegalStateException(
+ "finishBroadcast() called outside of a broadcast");
}
+
+ Object[] active = mActiveBroadcast;
+ if (active != null) {
+ final int N = mBroadcastCount;
+ for (int i=0; i<N; i++) {
+ active[i] = null;
+ }
+ }
+
+ mBroadcastCount = -1;
}
-
- mBroadcastCount = -1;
}
/**
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 9e8103a..3ae28fd 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -81,6 +81,8 @@
public static final long TRACE_TAG_SYSTEM_SERVER = 1L << 19;
/** @hide */
public static final long TRACE_TAG_DATABASE = 1L << 20;
+ /** @hide */
+ public static final long TRACE_TAG_NETWORK = 1L << 21;
private static final long TRACE_TAG_NOT_READY = 1L << 63;
private static final int MAX_SECTION_NAME_LEN = 127;
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 1ec00db..11b9606 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -142,13 +142,13 @@
final boolean zenMuted = isZenMuted();
mSeekBar.setEnabled(!zenMuted);
if (zenMuted) {
- mSeekBar.setProgress(mLastAudibleStreamVolume);
+ mSeekBar.setProgress(mLastAudibleStreamVolume, true);
} else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
- mSeekBar.setProgress(0);
+ mSeekBar.setProgress(0, true);
} else if (mMuted) {
- mSeekBar.setProgress(0);
+ mSeekBar.setProgress(0, true);
} else {
- mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
+ mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume, true);
}
}
@@ -313,13 +313,13 @@
public void muteVolume() {
if (mVolumeBeforeMute != -1) {
- mSeekBar.setProgress(mVolumeBeforeMute);
+ mSeekBar.setProgress(mVolumeBeforeMute, true);
postSetVolume(mVolumeBeforeMute);
postStartSample();
mVolumeBeforeMute = -1;
} else {
mVolumeBeforeMute = mSeekBar.getProgress();
- mSeekBar.setProgress(0);
+ mSeekBar.setProgress(0, true);
postStopSample();
postSetVolume(0);
}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index c495e6c..a1763c0 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -5166,7 +5166,7 @@
* </table>
*/
public static final class PhoneLookup implements BaseColumns, PhoneLookupColumns,
- ContactsColumns, ContactOptionsColumns {
+ ContactsColumns, ContactOptionsColumns, ContactNameColumns {
/**
* This utility class cannot be instantiated
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0ee0fc9..f8cf9ab 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5947,6 +5947,36 @@
INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF;
/**
+ * What happens when the user presses the Back button while in-call
+ * and the screen is on.<br/>
+ * <b>Values:</b><br/>
+ * 0 - The Back buttons does nothing different.<br/>
+ * 1 - The Back button hangs up the current call.<br/>
+ *
+ * @hide
+ */
+ public static final String INCALL_BACK_BUTTON_BEHAVIOR = "incall_back_button_behavior";
+
+ /**
+ * INCALL_BACK_BUTTON_BEHAVIOR value for no action.
+ * @hide
+ */
+ public static final int INCALL_BACK_BUTTON_BEHAVIOR_NONE = 0x0;
+
+ /**
+ * INCALL_BACK_BUTTON_BEHAVIOR value for "hang up".
+ * @hide
+ */
+ public static final int INCALL_BACK_BUTTON_BEHAVIOR_HANGUP = 0x1;
+
+ /**
+ * INCALL_POWER_BUTTON_BEHAVIOR default value.
+ * @hide
+ */
+ public static final int INCALL_BACK_BUTTON_BEHAVIOR_DEFAULT =
+ INCALL_BACK_BUTTON_BEHAVIOR_NONE;
+
+ /**
* Whether the device should wake when the wake gesture sensor detects motion.
* @hide
*/
@@ -6406,21 +6436,6 @@
public static final String WEB_ACTION_ENABLED = "web_action_enabled";
/**
- * The uptime when tasks were last persisted. This is used to adjust the previous task
- * active times to be relative to the current boot time.
- * @hide
- */
- public static final String TASK_PERSISTER_LAST_WRITE_UPTIME = "task_persister_write_uptime";
-
- /**
- * Used by Overview to keep track of the last visible task's active time to determine what
- * should tasks be visible.
- * @hide
- */
- public static final String OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME =
- "overview_last_visible_task_active_uptime";
-
- /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -7543,6 +7558,12 @@
"network_switch_notification_rate_limit_millis";
/**
+ * Whether to automatically switch away from wifi networks that lose Internet access.
+ * @hide
+ */
+ public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi";
+
+ /**
* Whether Wifi display is enabled/disabled
* 0=disabled. 1=enabled.
* @hide
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index 9d4b0a4..c8358a6 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -27,6 +27,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
@@ -126,6 +127,42 @@
}
/**
+ * Request that the provider be rebound, after a previous call to (@link requestUnbind).
+ *
+ * <p>This method will fail for providers that have not been granted the permission by the user.
+ */
+ public static final void requestRebind(ComponentName componentName) {
+ INotificationManager noMan = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ try {
+ noMan.requestBindProvider(componentName);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request that the provider service be unbound.
+ *
+ * <p>This will no longer receive subscription updates and will not be able to update the
+ * state of conditions until {@link #requestRebind(ComponentName)} is called.
+ * The service will likely be killed by the system after this call.
+ *
+ * <p>The service should wait for the {@link #onConnected()} event before performing this
+ * operation.
+ */
+ public final void requestUnbind() {
+ INotificationManager noMan = getNotificationInterface();
+ try {
+ noMan.requestUnbindProvider(mProvider);
+ // Disable future messages.
+ mProvider = null;
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Informs the notification manager that the state of a Condition has changed. Use this method
* to put the system into Do Not Disturb mode or request that it exits Do Not Disturb mode. This
* call will be ignored unless there is an enabled {@link android.app.AutomaticZenRule} owned by
@@ -193,6 +230,9 @@
@Override
public void handleMessage(Message msg) {
String name = null;
+ if (!isBound()) {
+ return;
+ }
try {
switch(msg.what) {
case ON_CONNECTED:
diff --git a/core/java/android/service/notification/NotificationRankerService.java b/core/java/android/service/notification/NotificationRankerService.java
index ee5361a..261d82d 100644
--- a/core/java/android/service/notification/NotificationRankerService.java
+++ b/core/java/android/service/notification/NotificationRankerService.java
@@ -99,6 +99,9 @@
/** Autobundled summary notification was canceled because its group was unbundled */
public static final int REASON_UNAUTOBUNDLED = 16;
+ /** Notification was canceled by the user banning the channel. */
+ public static final int REASON_CHANNEL_BANNED = 17;
+
private Handler mHandler;
/** @hide */
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 474a9b6..b225018 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1,7 +1,7 @@
/**
* Copyright (c) 2014, The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
+ * Licensed under the Apache License, 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
*
@@ -398,18 +398,13 @@
}
}
- public static ZenModeConfig readXml(XmlPullParser parser, Migration migration)
+ public static ZenModeConfig readXml(XmlPullParser parser)
throws XmlPullParserException, IOException {
int type = parser.getEventType();
if (type != XmlPullParser.START_TAG) return null;
String tag = parser.getName();
if (!ZEN_TAG.equals(tag)) return null;
final ZenModeConfig rt = new ZenModeConfig();
- final int version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION);
- if (version == 1) {
- final XmlV1 v1 = XmlV1.readXml(parser);
- return migration.migrate(v1);
- }
rt.user = safeInt(parser, ZEN_ATT_USER, rt.user);
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
tag = parser.getName();
@@ -1241,122 +1236,6 @@
};
}
- // Legacy config
- public static final class XmlV1 {
- public static final String SLEEP_MODE_NIGHTS = "nights";
- public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
- public static final String SLEEP_MODE_DAYS_PREFIX = "days:";
-
- private static final String EXIT_CONDITION_TAG = "exitCondition";
- private static final String EXIT_CONDITION_ATT_COMPONENT = "component";
- private static final String SLEEP_TAG = "sleep";
- private static final String SLEEP_ATT_MODE = "mode";
- private static final String SLEEP_ATT_NONE = "none";
-
- private static final String SLEEP_ATT_START_HR = "startHour";
- private static final String SLEEP_ATT_START_MIN = "startMin";
- private static final String SLEEP_ATT_END_HR = "endHour";
- private static final String SLEEP_ATT_END_MIN = "endMin";
-
- public boolean allowCalls;
- public boolean allowMessages;
- public boolean allowReminders = DEFAULT_ALLOW_REMINDERS;
- public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
- public int allowFrom = SOURCE_ANYONE;
-
- public String sleepMode; // nights, weeknights, days:1,2,3 Calendar.days
- public int sleepStartHour; // 0-23
- public int sleepStartMinute; // 0-59
- public int sleepEndHour;
- public int sleepEndMinute;
- public boolean sleepNone; // false = priority, true = none
- public ComponentName[] conditionComponents;
- public Uri[] conditionIds;
- public Condition exitCondition; // manual exit condition
- public ComponentName exitConditionComponent; // manual exit condition component
-
- private static boolean isValidSleepMode(String sleepMode) {
- return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
- || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null;
- }
-
- public static int[] tryParseDays(String sleepMode) {
- if (sleepMode == null) return null;
- sleepMode = sleepMode.trim();
- if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS;
- if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS;
- if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null;
- if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null;
- return tryParseDayList(sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()), ",");
- }
-
- public static XmlV1 readXml(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- int type;
- String tag;
- XmlV1 rt = new XmlV1();
- final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
- final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
- tag = parser.getName();
- if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
- if (!conditionComponents.isEmpty()) {
- rt.conditionComponents = conditionComponents
- .toArray(new ComponentName[conditionComponents.size()]);
- rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
- }
- return rt;
- }
- if (type == XmlPullParser.START_TAG) {
- if (ALLOW_TAG.equals(tag)) {
- rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
- rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
- rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
- DEFAULT_ALLOW_REMINDERS);
- rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS,
- DEFAULT_ALLOW_EVENTS);
- rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE);
- if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
- throw new IndexOutOfBoundsException("bad source in config:"
- + rt.allowFrom);
- }
- } else if (SLEEP_TAG.equals(tag)) {
- final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
- rt.sleepMode = isValidSleepMode(mode)? mode : null;
- rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false);
- final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
- final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
- final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
- final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
- rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
- rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
- rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
- rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
- } else if (CONDITION_TAG.equals(tag)) {
- final ComponentName component =
- safeComponentName(parser, CONDITION_ATT_COMPONENT);
- final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
- if (component != null && conditionId != null) {
- conditionComponents.add(component);
- conditionIds.add(conditionId);
- }
- } else if (EXIT_CONDITION_TAG.equals(tag)) {
- rt.exitCondition = readConditionXml(parser);
- if (rt.exitCondition != null) {
- rt.exitConditionComponent =
- safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT);
- }
- }
- }
- }
- throw new IllegalStateException("Failed to reach END_DOCUMENT");
- }
- }
-
- public interface Migration {
- ZenModeConfig migrate(XmlV1 v1);
- }
-
public static class Diff {
private final ArrayList<String> lines = new ArrayList<>();
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 3c7741d..9ac8996 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -17,8 +17,11 @@
package android.text;
import android.graphics.Paint;
+import android.graphics.Rect;
+import android.text.style.ReplacementSpan;
import android.text.style.UpdateLayout;
import android.text.style.WrapTogetherSpan;
+import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -300,7 +303,6 @@
.setHyphenationFrequency(mHyphenationFrequency);
reflowed.generate(b, false, true);
int n = reflowed.getLineCount();
-
// If the new layout has a blank line at the end, but it is not
// the very end of the buffer, then we already have a line that
// starts there, so disregard the blank line.
@@ -345,9 +347,10 @@
Directions[] objects = new Directions[1];
for (int i = 0; i < n; i++) {
- ints[START] = reflowed.getLineStart(i) |
- (reflowed.getParagraphDirection(i) << DIR_SHIFT) |
- (reflowed.getLineContainsTab(i) ? TAB_MASK : 0);
+ final int start = reflowed.getLineStart(i);
+ ints[START] = start;
+ ints[DIR] |= reflowed.getParagraphDirection(i) << DIR_SHIFT;
+ ints[TAB] |= reflowed.getLineContainsTab(i) ? TAB_MASK : 0;
int top = reflowed.getLineTop(i) + startv;
if (i > 0)
@@ -361,7 +364,11 @@
ints[DESCENT] = desc;
objects[0] = reflowed.getLineDirections(i);
- ints[HYPHEN] = reflowed.getHyphen(i);
+ final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
+ ints[HYPHEN] = reflowed.getHyphen(i) & HYPHEN_MASK;
+ ints[MAY_PROTRUDE_FROM_TOP_OR_BOTTOM] |=
+ contentMayProtrudeFromLineTopOrBottom(text, start, end) ?
+ MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK : 0;
if (mEllipsize) {
ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
@@ -381,6 +388,21 @@
}
}
+ private boolean contentMayProtrudeFromLineTopOrBottom(CharSequence text, int start, int end) {
+ if (text instanceof Spanned) {
+ final Spanned spanned = (Spanned) text;
+ if (spanned.getSpans(start, end, ReplacementSpan.class).length > 0) {
+ return true;
+ }
+ }
+ // Spans other than ReplacementSpan can be ignored because line top and bottom are
+ // disjunction of all tops and bottoms, although it's not optimal.
+ final Paint paint = getPaint();
+ paint.getTextBounds(text, start, end, mTempRect);
+ final Paint.FontMetricsInt fm = paint.getFontMetricsInt();
+ return mTempRect.top < fm.top || mTempRect.bottom > fm.bottom;
+ }
+
/**
* Create the initial block structure, cutting the text into blocks of at least
* BLOCK_MINIMUM_CHARACTER_SIZE characters, aligned on the ends of paragraphs.
@@ -409,17 +431,41 @@
}
/**
+ * @hide
+ */
+ public ArraySet<Integer> getBlocksAlwaysNeedToBeRedrawn() {
+ return mBlocksAlwaysNeedToBeRedrawn;
+ }
+
+ private void updateAlwaysNeedsToBeRedrawn(int blockIndex) {
+ int startLine = blockIndex == 0 ? 0 : (mBlockEndLines[blockIndex - 1] + 1);
+ int endLine = mBlockEndLines[blockIndex];
+ for (int i = startLine; i <= endLine; i++) {
+ if (getContentMayProtrudeFromTopOrBottom(i)) {
+ if (mBlocksAlwaysNeedToBeRedrawn == null) {
+ mBlocksAlwaysNeedToBeRedrawn = new ArraySet<>();
+ }
+ mBlocksAlwaysNeedToBeRedrawn.add(blockIndex);
+ return;
+ }
+ }
+ if (mBlocksAlwaysNeedToBeRedrawn != null) {
+ mBlocksAlwaysNeedToBeRedrawn.remove(blockIndex);
+ }
+ }
+
+ /**
* Create a new block, ending at the specified character offset.
* A block will actually be created only if has at least one line, i.e. this offset is
* not on the end line of the previous block.
*/
private void addBlockAtOffset(int offset) {
final int line = getLineForOffset(offset);
-
if (mBlockEndLines == null) {
// Initial creation of the array, no test on previous block ending line
mBlockEndLines = ArrayUtils.newUnpaddedIntArray(1);
mBlockEndLines[mNumberOfBlocks] = line;
+ updateAlwaysNeedsToBeRedrawn(mNumberOfBlocks);
mNumberOfBlocks++;
return;
}
@@ -427,6 +473,7 @@
final int previousBlockEndLine = mBlockEndLines[mNumberOfBlocks - 1];
if (line > previousBlockEndLine) {
mBlockEndLines = GrowingArrayUtils.append(mBlockEndLines, mNumberOfBlocks, line);
+ updateAlwaysNeedsToBeRedrawn(mNumberOfBlocks);
mNumberOfBlocks++;
}
}
@@ -506,13 +553,25 @@
blockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
mBlockEndLines = blockEndLines;
mBlockIndices = blockIndices;
- } else {
+ } else if (numAddedBlocks + numRemovedBlocks != 0) {
System.arraycopy(mBlockEndLines, lastBlock + 1,
mBlockEndLines, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
System.arraycopy(mBlockIndices, lastBlock + 1,
mBlockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
}
+ if (numAddedBlocks + numRemovedBlocks != 0 && mBlocksAlwaysNeedToBeRedrawn != null) {
+ final ArraySet<Integer> set = new ArraySet<>();
+ for (int i = 0; i < mBlocksAlwaysNeedToBeRedrawn.size(); i++) {
+ Integer block = mBlocksAlwaysNeedToBeRedrawn.valueAt(i);
+ if (block > firstBlock) {
+ block += numAddedBlocks - numRemovedBlocks;
+ }
+ set.add(block);
+ }
+ mBlocksAlwaysNeedToBeRedrawn = set;
+ }
+
mNumberOfBlocks = newNumberOfBlocks;
int newFirstChangedBlock;
final int deltaLines = newLineCount - (endLine - startLine + 1);
@@ -531,18 +590,21 @@
int blockIndex = firstBlock;
if (createBlockBefore) {
mBlockEndLines[blockIndex] = startLine - 1;
+ updateAlwaysNeedsToBeRedrawn(blockIndex);
mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
blockIndex++;
}
if (createBlock) {
mBlockEndLines[blockIndex] = startLine + newLineCount - 1;
+ updateAlwaysNeedsToBeRedrawn(blockIndex);
mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
blockIndex++;
}
if (createBlockAfter) {
mBlockEndLines[blockIndex] = lastBlockEndLine + deltaLines;
+ updateAlwaysNeedsToBeRedrawn(blockIndex);
mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
}
}
@@ -577,6 +639,21 @@
/**
* @hide
*/
+ public int getBlockIndex(int index) {
+ return mBlockIndices[index];
+ }
+
+ /**
+ * @hide
+ * @param index
+ */
+ public void setBlockIndex(int index, int blockIndex) {
+ mBlockIndices[index] = blockIndex;
+ }
+
+ /**
+ * @hide
+ */
public int getNumberOfBlocks() {
return mNumberOfBlocks;
}
@@ -645,7 +722,12 @@
*/
@Override
public int getHyphen(int line) {
- return mInts.getValue(line, HYPHEN);
+ return mInts.getValue(line, HYPHEN) & HYPHEN_MASK;
+ }
+
+ private boolean getContentMayProtrudeFromTopOrBottom(int line) {
+ return (mInts.getValue(line, MAY_PROTRUDE_FROM_TOP_OR_BOTTOM)
+ & MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK) != 0;
}
@Override
@@ -741,6 +823,8 @@
// The indices of this block's display list in TextView's internal display list array or
// INVALID_BLOCK_INDEX if this block has been invalidated during an edition
private int[] mBlockIndices;
+ // Set of blocks that always need to be redrawn.
+ private ArraySet<Integer> mBlocksAlwaysNeedToBeRedrawn;
// Number of items actually currently being used in the above 2 arrays
private int mNumberOfBlocks;
// The first index of the blocks whose locations are changed
@@ -748,17 +832,22 @@
private int mTopPadding, mBottomPadding;
+ private Rect mTempRect = new Rect();
+
private static StaticLayout sStaticLayout = null;
private static StaticLayout.Builder sBuilder = null;
private static final Object[] sLock = new Object[0];
+ // START, DIR, and TAB share the same entry.
private static final int START = 0;
private static final int DIR = START;
private static final int TAB = START;
private static final int TOP = 1;
private static final int DESCENT = 2;
+ // HYPHEN and MAY_PROTRUDE_FROM_TOP_OR_BOTTOM share the same entry.
private static final int HYPHEN = 3;
+ private static final int MAY_PROTRUDE_FROM_TOP_OR_BOTTOM = HYPHEN;
private static final int COLUMNS_NORMAL = 4;
private static final int ELLIPSIS_START = 4;
@@ -768,6 +857,8 @@
private static final int START_MASK = 0x1FFFFFFF;
private static final int DIR_SHIFT = 30;
private static final int TAB_MASK = 0x20000000;
+ private static final int HYPHEN_MASK = 0xFF;
+ private static final int MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK = 0x100;
private static final int ELLIPSIS_UNDEFINED = 0x80000000;
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index bb131a0..70d183d 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -1181,7 +1181,7 @@
*/
@Override
public int getHyphen(int line) {
- return mLines[mColumns * line + HYPHEN] & 0xff;
+ return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
}
/**
@@ -1295,6 +1295,7 @@
private static final int START_MASK = 0x1FFFFFFF;
private static final int DIR_SHIFT = 30;
private static final int TAB_MASK = 0x20000000;
+ private static final int HYPHEN_MASK = 0xFF;
private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index b0f15b5..a394f35 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -377,6 +377,10 @@
* The object is intended to provide local information about the drag and drop operation. For
* example, it can indicate whether the drag and drop operation is a copy or a move.
* <p>
+ * The local state is available only to views in the activity which has started the drag
+ * operation. In all other activities this method will return null
+ * </p>
+ * <p>
* This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}.
* </p>
* @return The local state object sent to the system by startDrag().
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index d2a3721..1d9f99f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -73,8 +73,8 @@
void clearForcedDisplaySize(int displayId);
int getInitialDisplayDensity(int displayId);
int getBaseDisplayDensity(int displayId);
- void setForcedDisplayDensity(int displayId, int density);
- void clearForcedDisplayDensity(int displayId);
+ void setForcedDisplayDensityForUser(int displayId, int density, int userId);
+ void clearForcedDisplayDensityForUser(int displayId, int userId);
void setForcedDisplayScalingMode(int displayId, int mode); // 0 = auto, 1 = disable
void setOverscan(int displayId, int left, int top, int right, int bottom);
@@ -101,7 +101,7 @@
* @param launchTaskBehind True if the token is been launched from behind.
* @param taskBounds Bounds to use when creating a new Task with the input task Id if
* the task doesn't exist yet.
- * @param configuration Configuration that is being used with this task.
+ * @param overrideConfig Override configuration that is being used with this task.
* @param taskResizeMode The resize mode of the task.
* @param alwaysFocusable True if the app windows are always focusable regardless of the stack
* they are in.
@@ -112,7 +112,7 @@
void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
int configChanges, boolean voiceInteraction, boolean launchTaskBehind,
- in Rect taskBounds, in Configuration configuration, int taskResizeMode,
+ in Rect taskBounds, in Configuration overrideConfig, int taskResizeMode,
boolean alwaysFocusable, boolean homeTask, int targetSdkVersion,
int rotationAnimationHint, boolean isOnTopLauncher);
/**
@@ -123,13 +123,14 @@
* if the task doesn't exist yet.
* @param taskBounds Bounds to use when creating a new Task with the input task Id if
* the task doesn't exist yet.
- * @param config Configuration that is being used with this task.
+ * @param overrideConfig Override configuration that is being used with this task.
* @param taskResizeMode The resize mode of the task.
* @param homeTask True if this is the task.
* @param isOnTopLauncher True if this task is an on-top launcher.
*/
void setAppTask(IBinder token, int taskId, int stackId, in Rect taskBounds,
- in Configuration config, int taskResizeMode, boolean homeTask, boolean isOnTopLauncher);
+ in Configuration overrideConfig, int taskResizeMode, boolean homeTask,
+ boolean isOnTopLauncher);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
int getAppOrientation(IApplicationToken token);
void setFocusedApp(IBinder token, boolean moveFocusNow);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 3e72192..9019e87 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.TestApi;
import android.graphics.Matrix;
import android.os.Parcel;
import android.os.Parcelable;
@@ -2355,6 +2356,7 @@
* @see #getActionButton()
* @hide
*/
+ @TestApi
public final void setActionButton(int button) {
nativeSetActionButton(mNativePtr, button);
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index f92d83a..9f46f3f 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -33,6 +33,18 @@
/**
* Handle onto a raw buffer that is being managed by the screen compositor.
+ *
+ * <p>A Surface is generally created by or from a consumer of image buffers (such as a
+ * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, or
+ * {@link android.renderscript.Allocation}), and is handed to some kind of producer (such as
+ * {@link android.opengl.EGL14#eglCreateWindowSurface(android.opengl.EGLDisplay,android.opengl.EGLConfig,java.lang.Object,int[],int) OpenGL},
+ * {@link android.media.MediaPlayer#setSurface MediaPlayer}, or
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession CameraDevice}) to draw
+ * into.</p>
+ *
+ * <p><strong>Note:</strong> A Surface acts like a
+ * {@link java.lang.ref.WeakReference weak reference} to the consumer it is associated with. By
+ * itself it will not keep its parent consumer from being reclaimed.</p>
*/
public class Surface implements Parcelable {
private static final String TAG = "Surface";
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 1034275..ce390a2 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -883,8 +883,14 @@
nSerializeDisplayListTree(mNativeProxy);
}
- public static int copySurfaceInto(Surface surface, Bitmap bitmap) {
- return nCopySurfaceInto(surface, bitmap);
+ public static int copySurfaceInto(Surface surface, Rect srcRect, Bitmap bitmap) {
+ if (srcRect == null) {
+ // Empty rect means entire surface
+ return nCopySurfaceInto(surface, 0, 0, 0, 0, bitmap);
+ } else {
+ return nCopySurfaceInto(surface, srcRect.left, srcRect.top,
+ srcRect.right, srcRect.bottom, bitmap);
+ }
}
@Override
@@ -1036,5 +1042,6 @@
private static native long nAddFrameMetricsObserver(long nativeProxy, FrameMetricsObserver observer);
private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver);
- private static native int nCopySurfaceInto(Surface surface, Bitmap bitmap);
+ private static native int nCopySurfaceInto(Surface surface,
+ int srcLeft, int srcTop, int srcRight, int srcBottom, Bitmap bitmap);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index aa17e80..a7337d2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6928,13 +6928,7 @@
info.setVisibleToUser(isVisibleToUser());
- if ((mAttachInfo != null) && ((mAttachInfo.mAccessibilityFetchFlags
- & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0)) {
- info.setImportantForAccessibility(isImportantForAccessibility());
- } else {
- info.setImportantForAccessibility(true);
- }
-
+ info.setImportantForAccessibility(isImportantForAccessibility());
info.setPackageName(mContext.getPackageName());
info.setClassName(getAccessibilityClassName());
info.setContentDescription(getContentDescription());
@@ -19108,7 +19102,7 @@
return mAttachInfo.mTreeObserver;
}
if (mFloatingTreeObserver == null) {
- mFloatingTreeObserver = new ViewTreeObserver();
+ mFloatingTreeObserver = new ViewTreeObserver(mContext);
}
return mFloatingTreeObserver;
}
@@ -20663,8 +20657,10 @@
* @param shadowBuilder A {@link android.view.View.DragShadowBuilder} object for building the
* drag shadow.
* @param myLocalState An {@link java.lang.Object} containing local data about the drag and
- * drop operation. This Object is put into every DragEvent object sent by the system during the
- * current drag.
+ * drop operation. When dispatching drag events to views in the same activity this object
+ * will be available through {@link android.view.DragEvent#getLocalState()}. Views in other
+ * activities will not have access to this data ({@link android.view.DragEvent#getLocalState()}
+ * will return null).
* <p>
* myLocalState is a lightweight mechanism for the sending information from the dragged View
* to the target Views. For example, it can contain flags that differentiate between a
@@ -23057,7 +23053,7 @@
* The view tree observer used to dispatch global events like
* layout, pre-draw, touch mode change, etc.
*/
- final ViewTreeObserver mTreeObserver = new ViewTreeObserver();
+ final ViewTreeObserver mTreeObserver;
/**
* A Canvas used by the view hierarchy to perform bitmap caching.
@@ -23179,7 +23175,8 @@
* @param handler the events handler the view must use
*/
AttachInfo(IWindowSession session, IWindow window, Display display,
- ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
+ ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,
+ Context context) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
@@ -23187,6 +23184,7 @@
mViewRootImpl = viewRootImpl;
mHandler = handler;
mRootCallbacks = effectPlayer;
+ mTreeObserver = new ViewTreeObserver(context);
}
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 6f9768f..e1ff0d6 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -846,6 +846,8 @@
view.post(new Runnable() {
@Override
public void run() {
+ encoder.addProperty("window:left", view.mAttachInfo.mWindowLeft);
+ encoder.addProperty("window:top", view.mAttachInfo.mWindowTop);
view.encode(encoder);
latch.countDown();
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 2a52b53..030fff1 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -45,6 +45,7 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -3144,6 +3145,25 @@
}
}
+ /** @hide */
+ @Override
+ public void notifySubtreeAccessibilityStateChangedIfNeeded() {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
+ return;
+ }
+ // If something important for a11y is happening in this subtree, make sure it's dispatched
+ // from a view that is important for a11y so it doesn't get lost.
+ if ((getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
+ && !isImportantForAccessibility() && (getChildCount() > 0)) {
+ ViewParent a11yParent = getParentForAccessibility();
+ if (a11yParent instanceof View) {
+ ((View) a11yParent).notifySubtreeAccessibilityStateChangedIfNeeded();
+ return;
+ }
+ }
+ super.notifySubtreeAccessibilityStateChangedIfNeeded();
+ }
+
@Override
void resetSubtreeAccessibilityStateChanged() {
super.resetSubtreeAccessibilityStateChanged();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ece0e1b..3bbf75e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -429,7 +429,8 @@
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mAdded = false;
- mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
+ mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
+ context);
mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityInteractionConnectionManager =
new AccessibilityInteractionConnectionManager();
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 521fd31..7dd2fc2 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -16,8 +16,11 @@
package android.view;
+import android.content.Context;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Build;
+import android.util.Log;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -49,7 +52,9 @@
private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners;
// These listeners cannot be mutated during dispatch
+ private boolean mInDispatchOnDraw;
private ArrayList<OnDrawListener> mOnDrawListeners;
+ private static boolean sIllegalOnDrawModificationIsFatal;
/** Remains false until #dispatchOnWindowShown() is called. If a listener registers after
* that the listener will be immediately called. */
@@ -327,7 +332,9 @@
/**
* Creates a new ViewTreeObserver. This constructor should not be called
*/
- ViewTreeObserver() {
+ ViewTreeObserver(Context context) {
+ sIllegalOnDrawModificationIsFatal =
+ context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.N_MR1;
}
/**
@@ -657,6 +664,15 @@
mOnDrawListeners = new ArrayList<OnDrawListener>();
}
+ if (mInDispatchOnDraw) {
+ IllegalStateException ex = new IllegalStateException(
+ "Cannot call addOnDrawListener inside of onDraw");
+ if (sIllegalOnDrawModificationIsFatal) {
+ throw ex;
+ } else {
+ Log.e("ViewTreeObserver", ex.getMessage(), ex);
+ }
+ }
mOnDrawListeners.add(listener);
}
@@ -676,6 +692,15 @@
if (mOnDrawListeners == null) {
return;
}
+ if (mInDispatchOnDraw) {
+ IllegalStateException ex = new IllegalStateException(
+ "Cannot call removeOnDrawListener inside of onDraw");
+ if (sIllegalOnDrawModificationIsFatal) {
+ throw ex;
+ } else {
+ Log.e("ViewTreeObserver", ex.getMessage(), ex);
+ }
+ }
mOnDrawListeners.remove(victim);
}
@@ -976,11 +1001,13 @@
*/
public final void dispatchOnDraw() {
if (mOnDrawListeners != null) {
+ mInDispatchOnDraw = true;
final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
int numListeners = listeners.size();
for (int i = 0; i < numListeners; ++i) {
listeners.get(i).onDraw();
}
+ mInDispatchOnDraw = false;
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index d382ca7..9127861 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -19,6 +19,7 @@
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
@@ -554,7 +555,7 @@
*/
private static final int VIRTUAL_DESCENDANT_ID_SHIFT = 32;
- private static final AtomicInteger sNumInstancesInUse = new AtomicInteger();
+ private static AtomicInteger sNumInstancesInUse;
/**
* Gets the accessibility view id which identifies a View in the view three.
@@ -2233,7 +2234,7 @@
*/
public void setText(CharSequence text) {
enforceNotSealed();
- mText = text;
+ mText = (text == null) ? null : text.subSequence(0, text.length());
}
/**
@@ -2250,7 +2251,7 @@
*/
public void setError(CharSequence error) {
enforceNotSealed();
- mError = error;
+ mError = (error == null) ? null : error.subSequence(0, error.length());
}
/**
@@ -2285,7 +2286,8 @@
*/
public void setContentDescription(CharSequence contentDescription) {
enforceNotSealed();
- mContentDescription = contentDescription;
+ mContentDescription = (contentDescription == null) ? null
+ : contentDescription.subSequence(0, contentDescription.length());
}
/**
@@ -2691,7 +2693,9 @@
*/
public static AccessibilityNodeInfo obtain() {
AccessibilityNodeInfo info = sPool.acquire();
- sNumInstancesInUse.incrementAndGet();
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse.incrementAndGet();
+ }
return (info != null) ? info : new AccessibilityNodeInfo();
}
@@ -2719,16 +2723,19 @@
public void recycle() {
clear();
sPool.release(this);
- sNumInstancesInUse.decrementAndGet();
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse.decrementAndGet();
+ }
}
/**
- * @return The number of instances of this class that have been obtained but not recycled.
+ * Specify a counter that will be incremented on obtain() and decremented on recycle()
*
* @hide
*/
- public static int getNumInstancesInUse() {
- return sNumInstancesInUse.get();
+ @TestApi
+ public static void setNumInstancesInUseCounter(AtomicInteger counter) {
+ sNumInstancesInUse = counter;
}
/**
@@ -3296,6 +3303,7 @@
builder.append("; enabled: ").append(isEnabled());
builder.append("; password: ").append(isPassword());
builder.append("; scrollable: ").append(isScrollable());
+ builder.append("; importantForAccessibility: ").append(isImportantForAccessibility());
builder.append("; actions: ").append(mActions);
return builder.toString();
@@ -3748,7 +3756,7 @@
* how to override the standard click action by adding a custom label:
* <pre>
* AccessibilityAction action = new AccessibilityAction(
- * AccessibilityAction.ACTION_ACTION_CLICK, getLocalizedLabel());
+ * AccessibilityAction.ACTION_CLICK.getId(), getLocalizedLabel());
* node.addAction(action);
* </pre>
*
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index f99690a..f2979bb 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -572,7 +572,8 @@
*/
public void setBeforeText(CharSequence beforeText) {
enforceNotSealed();
- mBeforeText = beforeText;
+ mBeforeText = (beforeText == null) ? null
+ : beforeText.subSequence(0, beforeText.length());
}
/**
@@ -593,7 +594,8 @@
*/
public void setContentDescription(CharSequence contentDescription) {
enforceNotSealed();
- mContentDescription = contentDescription;
+ mContentDescription = (contentDescription == null) ? null
+ : contentDescription.subSequence(0, contentDescription.length());
}
/**
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 8e308d6..3287298 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -17,6 +17,7 @@
package android.view.accessibility;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
@@ -83,7 +84,7 @@
private static final int MAX_POOL_SIZE = 10;
private static final SynchronizedPool<AccessibilityWindowInfo> sPool =
new SynchronizedPool<AccessibilityWindowInfo>(MAX_POOL_SIZE);
- private static final AtomicInteger sNumInstancesInUse = new AtomicInteger();
+ private static AtomicInteger sNumInstancesInUse;
// Data.
private int mType = UNDEFINED;
@@ -403,7 +404,9 @@
if (info == null) {
info = new AccessibilityWindowInfo();
}
- sNumInstancesInUse.incrementAndGet();
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse.incrementAndGet();
+ }
return info;
}
@@ -441,12 +444,15 @@
}
/**
- * @return The number of instances of this class that have been obtained but not recycled.
+ * Specify a counter that will be incremented on obtain() and decremented on recycle()
*
* @hide
*/
- public static int getNumInstancesInUse() {
- return sNumInstancesInUse.get();
+ @TestApi
+ public static void setNumInstancesInUseCounter(AtomicInteger counter) {
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse = counter;
+ }
}
/**
@@ -460,7 +466,9 @@
public void recycle() {
clear();
sPool.release(this);
- sNumInstancesInUse.decrementAndGet();
+ if (sNumInstancesInUse != null) {
+ sNumInstancesInUse.decrementAndGet();
+ }
}
@Override
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionCallback.aidl
deleted file mode 100644
index eeab4f2..0000000
--- a/core/java/android/view/accessibility/IAccessibilityInteractionCallback.aidl
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2011 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.view.accessibility;
-
-import android.view.accessibility.AccessibilityNodeInfo;
-import java.util.List;
-
-/**
- * Callback for specifying the result for an asynchronous request made
- * via calling a method on IAccessibilityInteractionCallback.
- *
- * @hide
- */
-oneway interface IAccessibilityInteractionCallback {
-
- /**
- * Sets the result of an async request that returns an {@link AccessibilityNodeInfo}.
- *
- * @param infos The result {@link AccessibilityNodeInfo}.
- * @param interactionId The interaction id to match the result with the request.
- */
- void setFindAccessibilityNodeInfoResult(in AccessibilityNodeInfo info, int interactionId);
-
- /**
- * Sets the result of an async request that returns {@link AccessibilityNodeInfo}s.
- *
- * @param infos The result {@link AccessibilityNodeInfo}s.
- * @param interactionId The interaction id to match the result with the request.
- */
- void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
- int interactionId);
-
- /**
- * Sets the result of a request to perform an accessibility action.
- *
- * @param Whether the action was performed.
- * @param interactionId The interaction id to match the result with the request.
- */
- void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
-}
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index 42ae1b3..c1a3ab7 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -16,7 +16,6 @@
package android.view.accessibility;
-import android.graphics.Point;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.List;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index e37cf96..ca370dc 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2065,12 +2065,10 @@
* have any input method subtype.
*/
public InputMethodSubtype getCurrentInputMethodSubtype() {
- synchronized (mH) {
- try {
- return mService.getCurrentInputMethodSubtype();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ try {
+ return mService.getCurrentInputMethodSubtype();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 119b8ed..184453b 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -7613,15 +7613,27 @@
final int position = mTargetPos;
final int lastPos = firstPos + childCount - 1;
- int viewTravelCount = 0;
+ // Account for the visible "portion" of the first / last child when we estimate
+ // how many screens we should travel to reach our target
+ final View firstChild = getChildAt(0);
+ final int firstChildHeight = firstChild.getHeight();
+ final View lastChild = getChildAt(childCount - 1);
+ final int lastChildHeight = lastChild.getHeight();
+ final float firstPositionVisiblePart = (firstChildHeight == 0.0f) ? 1.0f
+ : (float) (firstChildHeight + firstChild.getTop()) / firstChildHeight;
+ final float lastPositionVisiblePart = (lastChildHeight == 0.0f) ? 1.0f
+ : (float) (lastChildHeight + getHeight() - lastChild.getBottom())
+ / lastChildHeight;
+
+ float viewTravelCount = 0;
if (position < firstPos) {
- viewTravelCount = firstPos - position + 1;
+ viewTravelCount = firstPos - position + (1.0f - firstPositionVisiblePart) + 1;
} else if (position > lastPos) {
- viewTravelCount = position - lastPos;
+ viewTravelCount = position - lastPos + (1.0f - lastPositionVisiblePart);
}
// Estimate how many screens we should travel
- final float screenTravelCount = (float) viewTravelCount / childCount;
+ final float screenTravelCount = viewTravelCount / childCount;
final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
if (position < firstPos) {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a0447a6..bf49048 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -70,6 +70,7 @@
import android.text.style.SuggestionSpan;
import android.text.style.TextAppearanceSpan;
import android.text.style.URLSpan;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
@@ -176,11 +177,21 @@
InputMethodState mInputMethodState;
private static class TextRenderNode {
+ // Render node has 3 recording states:
+ // 1. Recorded operations are valid.
+ // #needsRecord() returns false, but needsToBeShifted is false.
+ // 2. Recorded operations are not valid, but just the position needed to be updated.
+ // #needsRecord() returns false, but needsToBeShifted is true.
+ // 3. Recorded operations are not valid. Need to record operations. #needsRecord() returns
+ // true.
RenderNode renderNode;
boolean isDirty;
+ // Becomes true when recorded operations can be reused, but the position has to be updated.
+ boolean needsToBeShifted;
public TextRenderNode(String name) {
- isDirty = true;
renderNode = RenderNode.create(name, null);
+ isDirty = true;
+ needsToBeShifted = true;
}
boolean needsRecord() {
return isDirty || !renderNode.isValid();
@@ -1686,85 +1697,138 @@
final int numberOfBlocks = dynamicLayout.getNumberOfBlocks();
final int indexFirstChangedBlock = dynamicLayout.getIndexFirstChangedBlock();
- int endOfPreviousBlock = -1;
- int searchStartIndex = 0;
- for (int i = 0; i < numberOfBlocks; i++) {
- int blockEndLine = blockEndLines[i];
- int blockIndex = blockIndices[i];
-
- final boolean blockIsInvalid = blockIndex == DynamicLayout.INVALID_BLOCK_INDEX;
- if (blockIsInvalid) {
- blockIndex = getAvailableDisplayListIndex(blockIndices, numberOfBlocks,
- searchStartIndex);
- // Note how dynamic layout's internal block indices get updated from Editor
- blockIndices[i] = blockIndex;
- if (mTextRenderNodes[blockIndex] != null) {
- mTextRenderNodes[blockIndex].isDirty = true;
+ final ArraySet<Integer> blockSet = dynamicLayout.getBlocksAlwaysNeedToBeRedrawn();
+ if (blockSet != null) {
+ for (int i = 0; i < blockSet.size(); i++) {
+ final int blockIndex = dynamicLayout.getBlockIndex(blockSet.valueAt(i));
+ if (blockIndex != DynamicLayout.INVALID_BLOCK_INDEX
+ && mTextRenderNodes[blockIndex] != null) {
+ mTextRenderNodes[blockIndex].needsToBeShifted = true;
}
- searchStartIndex = blockIndex + 1;
}
-
- if (mTextRenderNodes[blockIndex] == null) {
- mTextRenderNodes[blockIndex] =
- new TextRenderNode("Text " + blockIndex);
- }
-
- final boolean blockDisplayListIsInvalid =
- mTextRenderNodes[blockIndex].needsRecord();
- RenderNode blockDisplayList = mTextRenderNodes[blockIndex].renderNode;
- if (i >= indexFirstChangedBlock || blockDisplayListIsInvalid) {
- final int blockBeginLine = endOfPreviousBlock + 1;
- final int top = layout.getLineTop(blockBeginLine);
- final int bottom = layout.getLineBottom(blockEndLine);
- int left = 0;
- int right = mTextView.getWidth();
- if (mTextView.getHorizontallyScrolling()) {
- float min = Float.MAX_VALUE;
- float max = Float.MIN_VALUE;
- for (int line = blockBeginLine; line <= blockEndLine; line++) {
- min = Math.min(min, layout.getLineLeft(line));
- max = Math.max(max, layout.getLineRight(line));
- }
- left = (int) min;
- right = (int) (max + 0.5f);
- }
-
- // Rebuild display list if it is invalid
- if (blockDisplayListIsInvalid) {
- final DisplayListCanvas displayListCanvas = blockDisplayList.start(
- right - left, bottom - top);
- try {
- // drawText is always relative to TextView's origin, this translation
- // brings this range of text back to the top left corner of the viewport
- displayListCanvas.translate(-left, -top);
- layout.drawText(displayListCanvas, blockBeginLine, blockEndLine);
- mTextRenderNodes[blockIndex].isDirty = false;
- // No need to untranslate, previous context is popped after
- // drawDisplayList
- } finally {
- blockDisplayList.end(displayListCanvas);
- // Same as drawDisplayList below, handled by our TextView's parent
- blockDisplayList.setClipToBounds(false);
- }
- }
-
- // Valid disply list whose index is >= indexFirstChangedBlock
- // only needs to update its drawing location.
- blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
- }
-
- ((DisplayListCanvas) canvas).drawRenderNode(blockDisplayList);
-
- endOfPreviousBlock = blockEndLine;
}
- dynamicLayout.setIndexFirstChangedBlock(numberOfBlocks);
+ int startBlock = Arrays.binarySearch(blockEndLines, 0, numberOfBlocks, firstLine);
+ if (startBlock < 0) {
+ startBlock = -(startBlock + 1);
+ }
+ startBlock = Math.min(indexFirstChangedBlock, startBlock);
+
+ int startIndexToFindAvailableRenderNode = 0;
+ int lastIndex = numberOfBlocks;
+
+ for (int i = startBlock; i < numberOfBlocks; i++) {
+ final int blockIndex = blockIndices[i];
+ if (i >= indexFirstChangedBlock
+ && blockIndex != DynamicLayout.INVALID_BLOCK_INDEX
+ && mTextRenderNodes[blockIndex] != null) {
+ mTextRenderNodes[blockIndex].needsToBeShifted = true;
+ }
+ if (blockEndLines[i] < firstLine) {
+ // Blocks in [indexFirstChangedBlock, firstLine) are not redrawn here. They will
+ // be redrawn after they get scrolled into drawing range.
+ continue;
+ }
+ startIndexToFindAvailableRenderNode = drawHardwareAcceleratedInner(canvas, layout,
+ highlight, highlightPaint, cursorOffsetVertical, blockEndLines,
+ blockIndices, i, numberOfBlocks, startIndexToFindAvailableRenderNode);
+ if (blockEndLines[i] >= lastLine) {
+ lastIndex = Math.max(indexFirstChangedBlock, i + 1);
+ break;
+ }
+ }
+ if (blockSet != null) {
+ for (int i = 0; i < blockSet.size(); i++) {
+ final int block = blockSet.valueAt(i);
+ final int blockIndex = dynamicLayout.getBlockIndex(block);
+ if (blockIndex == DynamicLayout.INVALID_BLOCK_INDEX
+ || mTextRenderNodes[blockIndex] == null
+ || mTextRenderNodes[blockIndex].needsToBeShifted) {
+ startIndexToFindAvailableRenderNode = drawHardwareAcceleratedInner(canvas,
+ layout, highlight, highlightPaint, cursorOffsetVertical,
+ blockEndLines, blockIndices, block, numberOfBlocks,
+ startIndexToFindAvailableRenderNode);
+ }
+ }
+ }
+
+ dynamicLayout.setIndexFirstChangedBlock(lastIndex);
} else {
// Boring layout is used for empty and hint text
layout.drawText(canvas, firstLine, lastLine);
}
}
+ private int drawHardwareAcceleratedInner(Canvas canvas, Layout layout, Path highlight,
+ Paint highlightPaint, int cursorOffsetVertical, int[] blockEndLines,
+ int[] blockIndices, int blockInfoIndex, int numberOfBlocks,
+ int startIndexToFindAvailableRenderNode) {
+ final int blockEndLine = blockEndLines[blockInfoIndex];
+ int blockIndex = blockIndices[blockInfoIndex];
+
+ final boolean blockIsInvalid = blockIndex == DynamicLayout.INVALID_BLOCK_INDEX;
+ if (blockIsInvalid) {
+ blockIndex = getAvailableDisplayListIndex(blockIndices, numberOfBlocks,
+ startIndexToFindAvailableRenderNode);
+ // Note how dynamic layout's internal block indices get updated from Editor
+ blockIndices[blockInfoIndex] = blockIndex;
+ if (mTextRenderNodes[blockIndex] != null) {
+ mTextRenderNodes[blockIndex].isDirty = true;
+ }
+ startIndexToFindAvailableRenderNode = blockIndex + 1;
+ }
+
+ if (mTextRenderNodes[blockIndex] == null) {
+ mTextRenderNodes[blockIndex] = new TextRenderNode("Text " + blockIndex);
+ }
+
+ final boolean blockDisplayListIsInvalid = mTextRenderNodes[blockIndex].needsRecord();
+ RenderNode blockDisplayList = mTextRenderNodes[blockIndex].renderNode;
+ if (mTextRenderNodes[blockIndex].needsToBeShifted || blockDisplayListIsInvalid) {
+ final int blockBeginLine = blockInfoIndex == 0 ?
+ 0 : blockEndLines[blockInfoIndex - 1] + 1;
+ final int top = layout.getLineTop(blockBeginLine);
+ final int bottom = layout.getLineBottom(blockEndLine);
+ int left = 0;
+ int right = mTextView.getWidth();
+ if (mTextView.getHorizontallyScrolling()) {
+ float min = Float.MAX_VALUE;
+ float max = Float.MIN_VALUE;
+ for (int line = blockBeginLine; line <= blockEndLine; line++) {
+ min = Math.min(min, layout.getLineLeft(line));
+ max = Math.max(max, layout.getLineRight(line));
+ }
+ left = (int) min;
+ right = (int) (max + 0.5f);
+ }
+
+ // Rebuild display list if it is invalid
+ if (blockDisplayListIsInvalid) {
+ final DisplayListCanvas displayListCanvas = blockDisplayList.start(
+ right - left, bottom - top);
+ try {
+ // drawText is always relative to TextView's origin, this translation
+ // brings this range of text back to the top left corner of the viewport
+ displayListCanvas.translate(-left, -top);
+ layout.drawText(displayListCanvas, blockBeginLine, blockEndLine);
+ mTextRenderNodes[blockIndex].isDirty = false;
+ // No need to untranslate, previous context is popped after
+ // drawDisplayList
+ } finally {
+ blockDisplayList.end(displayListCanvas);
+ // Same as drawDisplayList below, handled by our TextView's parent
+ blockDisplayList.setClipToBounds(false);
+ }
+ }
+
+ // Valid display list only needs to update its drawing location.
+ blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
+ mTextRenderNodes[blockIndex].needsToBeShifted = false;
+ }
+ ((DisplayListCanvas) canvas).drawRenderNode(blockDisplayList);
+ return startIndexToFindAvailableRenderNode;
+ }
+
private int getAvailableDisplayListIndex(int[] blockIndices, int numberOfBlocks,
int searchStartIndex) {
int length = mTextRenderNodes.length;
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 82f5f01..72b2e31 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -16,6 +16,8 @@
package android.widget;
+import com.android.internal.R;
+
import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.TestApi;
@@ -30,10 +32,12 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
import android.text.TextUtils;
+import android.text.TextWatcher;
import android.text.method.NumberKeyListener;
import android.util.AttributeSet;
import android.util.SparseArray;
@@ -53,9 +57,6 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
-import com.android.internal.R;
-import libcore.icu.LocaleData;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -63,6 +64,8 @@
import java.util.List;
import java.util.Locale;
+import libcore.icu.LocaleData;
+
/**
* A widget that enables the user to select a number from a predefined range.
* There are two flavors of this widget and which one is presented to the user
@@ -2008,7 +2011,7 @@
removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
}
if (mSetSelectionCommand != null) {
- removeCallbacks(mSetSelectionCommand);
+ mSetSelectionCommand.cancel();
}
if (mBeginSoftInputOnLongPressCommand != null) {
removeCallbacks(mBeginSoftInputOnLongPressCommand);
@@ -2050,18 +2053,14 @@
}
/**
- * Posts an {@link SetSelectionCommand} from the given <code>selectionStart
- * </code> to <code>selectionEnd</code>.
+ * Posts a {@link SetSelectionCommand} from the given
+ * {@code selectionStart} to {@code selectionEnd}.
*/
private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
if (mSetSelectionCommand == null) {
- mSetSelectionCommand = new SetSelectionCommand();
- } else {
- removeCallbacks(mSetSelectionCommand);
+ mSetSelectionCommand = new SetSelectionCommand(mInputText);
}
- mSetSelectionCommand.mSelectionStart = selectionStart;
- mSetSelectionCommand.mSelectionEnd = selectionEnd;
- post(mSetSelectionCommand);
+ mSetSelectionCommand.post(selectionStart, selectionEnd);
}
/**
@@ -2107,6 +2106,12 @@
@Override
public CharSequence filter(
CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
+ // We don't know what the output will be, so always cancel any
+ // pending set selection command.
+ if (mSetSelectionCommand != null) {
+ mSetSelectionCommand.cancel();
+ }
+
if (mDisplayedValues == null) {
CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
if (filtered == null) {
@@ -2254,12 +2259,39 @@
/**
* Command for setting the input text selection.
*/
- class SetSelectionCommand implements Runnable {
- private int mSelectionStart;
+ private static class SetSelectionCommand implements Runnable {
+ private final EditText mInputText;
+ private int mSelectionStart;
private int mSelectionEnd;
+ /** Whether this runnable is currently posted. */
+ private boolean mPosted;
+
+ public SetSelectionCommand(EditText inputText) {
+ mInputText = inputText;
+ }
+
+ public void post(int selectionStart, int selectionEnd) {
+ mSelectionStart = selectionStart;
+ mSelectionEnd = selectionEnd;
+
+ if (!mPosted) {
+ mInputText.post(this);
+ mPosted = true;
+ }
+ }
+
+ public void cancel() {
+ if (mPosted) {
+ mInputText.removeCallbacks(this);
+ mPosted = false;
+ }
+ }
+
+ @Override
public void run() {
+ mPosted = false;
mInputText.setSelection(mSelectionStart, mSelectionEnd);
}
}
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index 50569d7..9938789 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -89,8 +89,9 @@
* means no bounce. This behavior is no longer supported and this coefficient has no effect.
* @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. This
* behavior is no longer supported and this coefficient has no effect.
- * !deprecated Use {!link #OverScroller(Context, Interpolator, boolean)} instead.
+ * @deprecated Use {@link #OverScroller(Context, Interpolator)} instead.
*/
+ @Deprecated
public OverScroller(Context context, Interpolator interpolator,
float bounceCoefficientX, float bounceCoefficientY) {
this(context, interpolator, true);
@@ -107,8 +108,9 @@
* @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. This
* behavior is no longer supported and this coefficient has no effect.
* @param flywheel If true, successive fling motions will keep on increasing scroll speed.
- * !deprecated Use {!link OverScroller(Context, Interpolator, boolean)} instead.
+ * @deprecated Use {@link #OverScroller(Context, Interpolator)} instead.
*/
+ @Deprecated
public OverScroller(Context context, Interpolator interpolator,
float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) {
this(context, interpolator, flywheel);
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index c92ee15..5e73a63 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -1758,11 +1758,22 @@
*/
public int getMaxAvailableHeight(
@NonNull View anchor, int yOffset, boolean ignoreBottomDecorations) {
- final Rect displayFrame = new Rect();
+ Rect displayFrame = null;
+ final Rect visibleDisplayFrame = new Rect();
+
+ anchor.getWindowVisibleDisplayFrame(visibleDisplayFrame);
if (ignoreBottomDecorations) {
+ // In the ignore bottom decorations case we want to
+ // still respect all other decorations so we use the inset visible
+ // frame on the top right and left and take the bottom
+ // value from the full frame.
+ displayFrame = new Rect();
anchor.getWindowDisplayFrame(displayFrame);
+ displayFrame.top = visibleDisplayFrame.top;
+ displayFrame.right = visibleDisplayFrame.right;
+ displayFrame.left = visibleDisplayFrame.left;
} else {
- anchor.getWindowVisibleDisplayFrame(displayFrame);
+ displayFrame = visibleDisplayFrame;
}
final int[] anchorPos = mTmpDrawingLocation;
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 5878cad..9139361 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -816,9 +816,11 @@
switch (heightMode) {
case MeasureSpec.AT_MOST:
- case MeasureSpec.UNSPECIFIED:
height = Math.min(getPreferredHeight(), height);
break;
+ case MeasureSpec.UNSPECIFIED:
+ height = getPreferredHeight();
+ break;
}
heightMode = MeasureSpec.EXACTLY;
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index e45e413..4efcb09 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -460,7 +460,7 @@
// the view isn't yet added, so let's try not to crash.
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
- mWM.removeView(mView);
+ mWM.removeViewImmediate(mView);
}
mView = null;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 0a4ac0d..1e26c92 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1469,7 +1469,7 @@
boolean found = false;
// Only loop to the end of into as it was before we started; no dupes in from.
for (int j = 0; j < intoCount; j++) {
- final ResolvedComponentInfo rci = into.get(i);
+ final ResolvedComponentInfo rci = into.get(j);
if (isSameResolvedComponent(newInfo, rci)) {
found = true;
rci.add(intent, newInfo);
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index a4b5a8e..cb2b019 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -145,7 +145,11 @@
if (itemType == TYPE_HEADER_SUGGESTED) {
textView.setText(R.string.language_picker_section_suggested);
} else {
- textView.setText(R.string.language_picker_section_all);
+ if (mCountryMode) {
+ textView.setText(R.string.region_picker_section_all);
+ } else {
+ textView.setText(R.string.language_picker_section_all);
+ }
}
textView.setTextLocale(Locale.getDefault());
break;
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 40b9ab6..59a3563 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -631,6 +631,13 @@
// an error.
ZygoteHooks.startZygoteNoThreadCreation();
+ // Zygote goes into its own process group.
+ try {
+ Os.setpgid(0, 0);
+ } catch (ErrnoException ex) {
+ throw new RuntimeException("Failed to setpgid(0,0)", ex);
+ }
+
try {
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ZygoteInit");
RuntimeInit.enableDdms();
diff --git a/core/java/com/android/internal/util/WakeupMessage.java b/core/java/com/android/internal/util/WakeupMessage.java
index 7d222c7..46098c5 100644
--- a/core/java/com/android/internal/util/WakeupMessage.java
+++ b/core/java/com/android/internal/util/WakeupMessage.java
@@ -108,7 +108,7 @@
}
if (stillScheduled) {
Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
- mHandler.handleMessage(msg);
+ mHandler.dispatchMessage(msg);
msg.recycle();
}
}
diff --git a/core/java/com/android/internal/widget/WatchListDecorLayout.java b/core/java/com/android/internal/widget/WatchListDecorLayout.java
index 538ceca..5b49611 100644
--- a/core/java/com/android/internal/widget/WatchListDecorLayout.java
+++ b/core/java/com/android/internal/widget/WatchListDecorLayout.java
@@ -306,8 +306,9 @@
if (mListView.getChildCount() > 0) {
if (mListView.getLastVisiblePosition() >= mListView.getCount() - 1) {
View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
- setScrolling(mBottomPanel,
- lastChild.getY() + lastChild.getHeight() - mBottomPanel.getTop());
+ setScrolling(mBottomPanel, Math.max(
+ 0,
+ lastChild.getY() + lastChild.getHeight() - mBottomPanel.getTop()));
} else {
// shift to hide the frame, last child is not the last position
setScrolling(mBottomPanel, mBottomPanel.getHeight());
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 2956175..18c4ee3 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -151,12 +151,12 @@
mPixelRef->unref();
}
-Bitmap::Bitmap(void* address, int fd,
+Bitmap::Bitmap(void* address, int fd, size_t mappedSize,
const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
: mPixelStorageType(PixelStorageType::Ashmem) {
mPixelStorage.ashmem.address = address;
mPixelStorage.ashmem.fd = fd;
- mPixelStorage.ashmem.size = ashmem_get_size_region(fd);
+ mPixelStorage.ashmem.size = mappedSize;
mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
// Note: this will trigger a call to onStrongRefDestroyed(), but
// we want the pixel ref to have a ref count of 0 at this point
@@ -1027,7 +1027,7 @@
// Map the pixels in place and take ownership of the ashmem region.
nativeBitmap = GraphicsJNI::mapAshmemPixelRef(env, bitmap.get(),
- ctable, dupFd, const_cast<void*>(blob.data()), !isMutable);
+ ctable, dupFd, const_cast<void*>(blob.data()), size, !isMutable);
SkSafeUnref(ctable);
if (!nativeBitmap) {
close(dupFd);
diff --git a/core/jni/android/graphics/Bitmap.h b/core/jni/android/graphics/Bitmap.h
index eadba5c..aaea178 100644
--- a/core/jni/android/graphics/Bitmap.h
+++ b/core/jni/android/graphics/Bitmap.h
@@ -51,8 +51,8 @@
const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable);
Bitmap(void* address, void* context, FreeFunc freeFunc,
const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable);
- Bitmap(void* address, int fd, const SkImageInfo& info, size_t rowBytes,
- SkColorTable* ctable);
+ Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info,
+ size_t rowBytes, SkColorTable* ctable);
const SkImageInfo& info() const;
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 695aed5..77799d6 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -384,9 +384,12 @@
// Set the alpha type for the decode.
SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
- const SkImageInfo decodeInfo = codec->getInfo().makeWH(size.width(), size.height())
- .makeColorType(decodeColorType)
- .makeAlphaType(alphaType);
+ // Enable legacy behavior to avoid any gamma correction. Android's assets are
+ // adjusted to expect a non-gamma correct premultiply.
+ sk_sp<SkColorSpace> colorSpace = nullptr;
+ const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType,
+ alphaType, colorSpace);
+
SkImageInfo bitmapInfo = decodeInfo;
if (decodeColorType == kGray_8_SkColorType) {
// The legacy implementation of BitmapFactory used kAlpha8 for
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index b5630d5..c6a51e8 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -613,7 +613,7 @@
return nullptr;
}
- android::Bitmap* wrapper = new android::Bitmap(addr, fd, info, rowBytes, ctable);
+ android::Bitmap* wrapper = new android::Bitmap(addr, fd, size, info, rowBytes, ctable);
wrapper->getSkBitmap(bitmap);
// since we're already allocated, we lockPixels right away
// HeapAllocator behaves this way too
@@ -623,7 +623,7 @@
}
android::Bitmap* GraphicsJNI::mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
- SkColorTable* ctable, int fd, void* addr, bool readOnly) {
+ SkColorTable* ctable, int fd, void* addr, size_t size, bool readOnly) {
const SkImageInfo& info = bitmap->info();
if (info.colorType() == kUnknown_SkColorType) {
doThrowIAE(env, "unknown bitmap configuration");
@@ -633,7 +633,8 @@
if (!addr) {
// Map existing ashmem region if not already mapped.
int flags = readOnly ? (PROT_READ) : (PROT_READ | PROT_WRITE);
- addr = mmap(NULL, ashmem_get_size_region(fd), flags, MAP_SHARED, fd, 0);
+ size = ashmem_get_size_region(fd);
+ addr = mmap(NULL, size, flags, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
return nullptr;
}
@@ -643,7 +644,7 @@
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
- android::Bitmap* wrapper = new android::Bitmap(addr, fd, info, rowBytes, ctable);
+ android::Bitmap* wrapper = new android::Bitmap(addr, fd, size, info, rowBytes, ctable);
wrapper->getSkBitmap(bitmap);
if (readOnly) {
bitmap->pixelRef()->setImmutable();
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 0f04f6d..89636db 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -100,7 +100,7 @@
SkColorTable* ctable);
static android::Bitmap* mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
- SkColorTable* ctable, int fd, void* addr, bool readOnly);
+ SkColorTable* ctable, int fd, void* addr, size_t size, bool readOnly);
/**
* Given a bitmap we natively allocate a memory block to store the contents
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 9459257..b926270 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -350,9 +350,16 @@
postData(msgType, dataPtr, NULL);
}
-void JNICameraContext::postRecordingFrameHandleTimestamp(nsecs_t, native_handle_t*) {
- // This is not needed at app layer. This should not be called because JNICameraContext cannot
- // start video recording.
+void JNICameraContext::postRecordingFrameHandleTimestamp(nsecs_t, native_handle_t* handle) {
+ // Video buffers are not needed at app layer so just return the video buffers here.
+ // This may be called when stagefright just releases camera but there are still outstanding
+ // video buffers.
+ if (mCamera != nullptr) {
+ mCamera->releaseRecordingFrameHandle(handle);
+ } else {
+ native_handle_close(handle);
+ native_handle_delete(handle);
+ }
}
void JNICameraContext::postMetadata(JNIEnv *env, int32_t msgType, camera_frame_metadata_t *metadata)
diff --git a/core/jni/android_hardware_UsbRequest.cpp b/core/jni/android_hardware_UsbRequest.cpp
index 399e7b1..4cbe3e4 100644
--- a/core/jni/android_hardware_UsbRequest.cpp
+++ b/core/jni/android_hardware_UsbRequest.cpp
@@ -47,7 +47,7 @@
struct usb_device* device = get_device_from_object(env, java_device);
if (!device) {
ALOGE("device null in native_init");
- return false;
+ return JNI_FALSE;
}
// construct an endpoint descriptor from the Java object fields
@@ -83,13 +83,13 @@
struct usb_request* request = get_request_from_object(env, thiz);
if (!request) {
ALOGE("request is closed in native_queue");
- return false;
+ return JNI_FALSE;
}
if (buffer && length) {
request->buffer = malloc(length);
if (!request->buffer)
- return false;
+ return JNI_FALSE;
memset(request->buffer, 0, length);
if (out) {
// copy data from Java buffer to native buffer
@@ -110,9 +110,9 @@
request->buffer = NULL;
}
env->DeleteGlobalRef((jobject)request->client_data);
- return false;
+ return JNI_FALSE;
}
- return true;
+ return JNI_TRUE;
}
static jint
@@ -141,13 +141,13 @@
struct usb_request* request = get_request_from_object(env, thiz);
if (!request) {
ALOGE("request is closed in native_queue");
- return false;
+ return JNI_FALSE;
}
if (buffer && length) {
request->buffer = env->GetDirectBufferAddress(buffer);
if (!request->buffer)
- return false;
+ return JNI_FALSE;
} else {
request->buffer = NULL;
}
@@ -161,9 +161,9 @@
if (usb_request_queue(request)) {
request->buffer = NULL;
env->DeleteGlobalRef((jobject)request->client_data);
- return false;
+ return JNI_FALSE;
}
- return true;
+ return JNI_TRUE;
}
static jint
@@ -185,7 +185,7 @@
struct usb_request* request = get_request_from_object(env, thiz);
if (!request) {
ALOGE("request is closed in native_cancel");
- return false;
+ return JNI_FALSE;
}
return (usb_request_cancel(request) == 0);
}
diff --git a/core/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp
index 3644410..8eb39e1 100644
--- a/core/jni/android_hardware_location_ContextHubService.cpp
+++ b/core/jni/android_hardware_location_ContextHubService.cpp
@@ -337,11 +337,15 @@
return 0;
}
-static void query_hub_for_apps(uint64_t appId, uint32_t hubHandle) {
+static void query_hub_for_apps(uint32_t hubHandle) {
hub_message_t msg;
query_apps_request_t queryMsg;
- queryMsg.app_name.id = appId;
+ // TODO(b/30835598): When we're able to tell which request our
+ // response matches, then we should allow this to be more
+ // targetted, instead of always being every app in the
+ // system.
+ queryMsg.app_name.id = ALL_APPS;
msg.message_type = CONTEXT_HUB_QUERY_APPS;
msg.message_len = sizeof(queryMsg);
@@ -354,9 +358,9 @@
}
}
-static void sendQueryForApps(uint64_t appId) {
+static void sendQueryForApps() {
for (int i = 0; i < db.hubInfo.numHubs; i++ ) {
- query_hub_for_apps(appId, i);
+ query_hub_for_apps(i);
}
}
@@ -386,23 +390,12 @@
static jint add_app_instance(const hub_app_info *appInfo, uint32_t hubHandle,
jint appInstanceHandle, JNIEnv *env) {
-
- ALOGI("Loading App");
-
// Not checking if the apps are indeed distinct
app_instance_info_s entry;
assert(appInfo);
- const char *action = "Updated";
- if (db.appInstances.count(appInstanceHandle) == 0) {
- action = "Added";
- appInstanceHandle = generate_id();
- if (appInstanceHandle < 0) {
- ALOGE("Cannot find resources to add app instance %" PRId32,
- appInstanceHandle);
- return -1;
- }
- }
+ const char *action =
+ (db.appInstances.count(appInstanceHandle) == 0) ? "Added" : "Updated";
entry.appInfo = *appInfo;
@@ -412,13 +405,14 @@
db.appInstances[appInstanceHandle] = entry;
- // Finally - let the service know of this app instance
+ // Finally - let the service know of this app instance, to populate
+ // the Java cache.
env->CallIntMethod(db.jniInfo.jContextHubService,
db.jniInfo.contextHubServiceAddAppInstance,
hubHandle, entry.instanceId, entry.truncName,
entry.appInfo.version);
- ALOGW("%s App 0x%" PRIx64 " on hub Handle %" PRId32
+ ALOGI("%s App 0x%" PRIx64 " on hub Handle %" PRId32
" as appInstance %" PRId32, action, entry.truncName,
entry.hubHandle, appInstanceHandle);
@@ -540,7 +534,7 @@
}
}
- sendQueryForApps(ALL_APPS);
+ sendQueryForApps();
} else {
ALOGW("No Context Hub Module present");
}
@@ -584,16 +578,63 @@
return -1;
}
- int numApps = msgLen/sizeof(hub_app_info);
- hub_app_info info;
+ int numApps = msgLen / sizeof(hub_app_info);
const hub_app_info *unalignedInfoAddr = (const hub_app_info*)msg;
- for (int i = 0; i < numApps; i++, unalignedInfoAddr++) {
- memcpy(&info, unalignedInfoAddr, sizeof(info));
+ // We use this information to sync our JNI and Java caches of nanoapp info.
+ // We want to accomplish two things here:
+ // 1) Remove entries from our caches which are stale, and pertained to
+ // apps no longer running on Context Hub.
+ // 2) Populate our caches with the latest information of all these apps.
+
+ // We make a couple of assumptions here:
+ // A) The JNI and Java caches are in sync with each other (this isn't
+ // necessarily true; any failure of a single call into Java land to
+ // update its cache will leave that cache in a bad state. For NYC,
+ // we're willing to tolerate this for now).
+ // B) The total number of apps is relatively small, so horribly inefficent
+ // algorithms aren't too painful.
+ // C) We're going to call this relatively infrequently, so its inefficency
+ // isn't a big impact.
+
+
+ // (1). Looking for stale cache entries. Yes, this is O(N^2). See
+ // assumption (B). Per assumption (A), it is sufficient to iterate
+ // over just the JNI cache.
+ auto end = db.appInstances.end();
+ for (auto current = db.appInstances.begin(); current != end; ) {
+ app_instance_info_s cache_entry = current->second;
+ // We perform our iteration here because if we call
+ // delete_app_instance() below, it will erase() this entry.
+ current++;
+ bool entryIsStale = true;
+ for (int i = 0; i < numApps; i++) {
+ // We use memcmp since this could be unaligned.
+ if (memcmp(&unalignedInfoAddr[i].app_name.id,
+ &cache_entry.appInfo.app_name.id,
+ sizeof(cache_entry.appInfo.app_name.id)) == 0) {
+ // We found a match; this entry is current.
+ entryIsStale = false;
+ break;
+ }
+ }
+ if (entryIsStale) {
+ delete_app_instance(cache_entry.instanceId, env);
+ }
+ }
+
+ // (2). Update our caches with the latest.
+ for (int i = 0; i < numApps; i++) {
+ hub_app_info query_info;
+ memcpy(&query_info, &unalignedInfoAddr[i], sizeof(query_info));
// We will only have one instance of the app
// TODO : Change this logic once we support multiple instances of the same app
- jint appInstance = get_app_instance_for_app_id(info.app_name.id);
- add_app_instance(&info, hubHandle, appInstance, env);
+ jint appInstance = get_app_instance_for_app_id(query_info.app_name.id);
+ if (appInstance == -1) {
+ // This is a previously unknown app, let's allocate an "id" for it.
+ appInstance = generate_id();
+ }
+ add_app_instance(&query_info, hubHandle, appInstance, env);
}
return 0;
@@ -717,7 +758,12 @@
ALOGW("Could not attach to JVM !");
success = false;
}
- sendQueryForApps(info->appInfo.app_name.id);
+ // While we just called add_app_instance above, our info->appInfo was
+ // incomplete (for example, the 'version' is hardcoded to -1). So we
+ // trigger an additional query to the CHRE, so we'll be able to get
+ // all the app "info", and have our JNI and Java caches with the
+ // full information.
+ sendQueryForApps();
} else {
ALOGW("Could not load the app successfully ! Unexpected failure");
*appInstanceHandle = INVALID_APP_ID;
@@ -747,24 +793,6 @@
return true;
}
-static void invalidateNanoApps(uint32_t hubHandle) {
- JNIEnv *env;
-
- if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
- ALOGW("Could not attach to JVM !");
- env = nullptr;
- }
-
- auto end = db.appInstances.end();
- for (auto current = db.appInstances.begin(); current != end; ) {
- app_instance_info_s info = current->second;
- current++;
- if (info.hubHandle == hubHandle) {
- delete_app_instance(info.instanceId, env);
- }
- }
-}
-
static int handle_os_message(uint32_t msgType, uint32_t hubHandle,
const uint8_t *msg, int msgLen) {
int retVal = -1;
@@ -832,8 +860,7 @@
ALOGW("Context Hub handle %d restarted", hubHandle);
closeTxn();
passOnOsResponse(hubHandle, msgType, &rsp, nullptr, 0);
- invalidateNanoApps(hubHandle);
- query_hub_for_apps(ALL_APPS, hubHandle);
+ query_hub_for_apps(hubHandle);
retVal = 0;
}
break;
@@ -1165,7 +1192,8 @@
if (retVal != 0) {
ALOGD("Send Message failure - %d", retVal);
if (msgType == CONTEXT_HUB_LOAD_APP) {
- closeLoadTxn(false, nullptr);
+ jint ignored;
+ closeLoadTxn(false, &ignored);
} else if (msgType == CONTEXT_HUB_UNLOAD_APP) {
closeUnloadTxn(false);
}
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 97833a0..7da0314 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -213,7 +213,9 @@
sp<hardware::IBinder> binder = JHwBinder::GetNativeContext(env, thiz);
status_t err = hardware::defaultServiceManager()->addService(
- String16(reinterpret_cast<const char16_t *>(serviceName)),
+ String16(
+ reinterpret_cast<const char16_t *>(serviceName),
+ env->GetStringLength(serviceNameObj)),
binder,
kVersion);
@@ -245,12 +247,15 @@
LOG(INFO) << "looking for service '"
<< String8(String16(
- reinterpret_cast<const char16_t *>(serviceName))).string()
+ reinterpret_cast<const char16_t *>(serviceName),
+ env->GetStringLength(serviceNameObj))).string()
<< "'";
sp<hardware::IBinder> service =
hardware::defaultServiceManager()->getService(
- String16(reinterpret_cast<const char16_t *>(serviceName)),
+ String16(
+ reinterpret_cast<const char16_t *>(serviceName),
+ env->GetStringLength(serviceNameObj)),
kVersion);
env->ReleaseStringCritical(serviceNameObj, serviceName);
diff --git a/core/jni/android_os_SELinux.cpp b/core/jni/android_os_SELinux.cpp
index 8ba77ae..4b68c0d 100644
--- a/core/jni/android_os_SELinux.cpp
+++ b/core/jni/android_os_SELinux.cpp
@@ -23,9 +23,9 @@
#include "selinux/selinux.h"
#include "selinux/android.h"
#include <errno.h>
+#include <memory>
#include <ScopedLocalRef.h>
#include <ScopedUtfChars.h>
-#include <UniquePtr.h>
namespace android {
@@ -34,7 +34,7 @@
freecon(p);
}
};
-typedef UniquePtr<char[], SecurityContext_Delete> Unique_SecurityContext;
+typedef std::unique_ptr<char[], SecurityContext_Delete> Unique_SecurityContext;
static jboolean isSELinuxDisabled = true;
@@ -112,7 +112,7 @@
return false;
}
- UniquePtr<ScopedUtfChars> context;
+ std::unique_ptr<ScopedUtfChars> context;
const char* context_c_str = NULL;
if (contextStr != NULL) {
context.reset(new ScopedUtfChars(env, contextStr));
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 87a8ef8..649cc1e 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -899,11 +899,12 @@
}
static jint android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env,
- jobject clazz, jobject jsurface, jobject jbitmap) {
+ jobject clazz, jobject jsurface, jint left, jint top,
+ jint right, jint bottom, jobject jbitmap) {
SkBitmap bitmap;
GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
sp<Surface> surface = android_view_Surface_getSurface(env, jsurface);
- return RenderProxy::copySurfaceInto(surface, &bitmap);
+ return RenderProxy::copySurfaceInto(surface, left, top, right, bottom, &bitmap);
}
// ----------------------------------------------------------------------------
@@ -1001,7 +1002,7 @@
{ "nRemoveFrameMetricsObserver",
"(JJ)V",
(void*)android_view_ThreadedRenderer_removeFrameMetricsObserver },
- { "nCopySurfaceInto", "(Landroid/view/Surface;Landroid/graphics/Bitmap;)I",
+ { "nCopySurfaceInto", "(Landroid/view/Surface;IIIILandroid/graphics/Bitmap;)I",
(void*)android_view_ThreadedRenderer_copySurfaceInto },
};
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 364ac44..7e01657 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -20,7 +20,6 @@
#include "core_jni_helpers.h"
#include <ScopedUtfChars.h>
-#include <UniquePtr.h>
#include <androidfw/ZipFileRO.h>
#include <androidfw/ZipUtils.h>
#include <utils/Log.h>
@@ -37,6 +36,7 @@
#include <sys/stat.h>
#include <sys/types.h>
+#include <memory>
#define APK_LIB "lib/"
#define APK_LIB_LEN (sizeof(APK_LIB) - 1)
@@ -398,7 +398,7 @@
return INSTALL_FAILED_INVALID_APK;
}
- UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
+ std::unique_ptr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
if (it.get() == NULL) {
return INSTALL_FAILED_INVALID_APK;
}
@@ -446,7 +446,7 @@
return INSTALL_FAILED_INVALID_APK;
}
- UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
+ std::unique_ptr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
if (it.get() == NULL) {
return INSTALL_FAILED_INVALID_APK;
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index f619c39..af117d1 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -32,6 +32,7 @@
#include <signal.h>
#include <stdlib.h>
#include <sys/capability.h>
+#include <sys/cdefs.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/resource.h>
@@ -685,11 +686,39 @@
static void com_android_internal_os_Zygote_nativeUnmountStorageOnInit(JNIEnv* env, jclass) {
// Zygote process unmount root storage space initially before every child processes are forked.
// Every forked child processes (include SystemServer) only mount their own root storage space
- // And no need unmount storage operation in MountEmulatedStorage method.
- // Zygote process does not utilize root storage spaces and unshared its mount namespace from the ART.
+ // and no need unmount storage operation in MountEmulatedStorage method.
+ // Zygote process does not utilize root storage spaces and unshares its mount namespace below.
+
+ // See storage config details at http://source.android.com/tech/storage/
+ // Create private mount namespace shared by all children
+ if (unshare(CLONE_NEWNS) == -1) {
+ RuntimeAbort(env, __LINE__, "Failed to unshare()");
+ return;
+ }
+
+ // Mark rootfs as being a slave so that changes from default
+ // namespace only flow into our children.
+ if (mount("rootfs", "/", nullptr, (MS_SLAVE | MS_REC), nullptr) == -1) {
+ RuntimeAbort(env, __LINE__, "Failed to mount() rootfs as MS_SLAVE");
+ return;
+ }
+
+ // Create a staging tmpfs that is shared by our children; they will
+ // bind mount storage into their respective private namespaces, which
+ // are isolated from each other.
+ const char* target_base = getenv("EMULATED_STORAGE_TARGET");
+ if (target_base != nullptr) {
+#define STRINGIFY_UID(x) __STRING(x)
+ if (mount("tmpfs", target_base, "tmpfs", MS_NOSUID | MS_NODEV,
+ "uid=0,gid=" STRINGIFY_UID(AID_SDCARD_R) ",mode=0751") == -1) {
+ ALOGE("Failed to mount tmpfs to %s", target_base);
+ RuntimeAbort(env, __LINE__, "Failed to mount tmpfs");
+ return;
+ }
+#undef STRINGIFY_UID
+ }
UnmountTree("/storage");
- return;
}
static const JNINativeMethod gMethods[] = {
diff --git a/core/res/assets/images/clock64.png b/core/res/assets/images/clock64.png
deleted file mode 100644
index 2e01e38..0000000
--- a/core/res/assets/images/clock64.png
+++ /dev/null
Binary files differ
diff --git a/core/res/assets/images/clock_font.png b/core/res/assets/images/clock_font.png
new file mode 100644
index 0000000..be927ae
--- /dev/null
+++ b/core/res/assets/images/clock_font.png
Binary files differ
diff --git a/core/res/res/anim-watch/progress_indeterminate_material.xml b/core/res/res/anim-watch/progress_indeterminate_material.xml
new file mode 100644
index 0000000..8f00d6c
--- /dev/null
+++ b/core/res/res/anim-watch/progress_indeterminate_material.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="3333"
+ android:interpolator="@interpolator/trim_start_interpolator"
+ android:propertyName="trimPathStart"
+ android:repeatCount="-1"
+ android:valueFrom="0"
+ android:valueTo="0.75"
+ android:valueType="floatType" />
+ <objectAnimator
+ android:duration="3333"
+ android:interpolator="@interpolator/trim_end_interpolator"
+ android:propertyName="trimPathEnd"
+ android:repeatCount="-1"
+ android:valueFrom="0"
+ android:valueTo="0.75"
+ android:valueType="floatType" />
+ <objectAnimator
+ android:duration="3333"
+ android:interpolator="@interpolator/trim_offset_interpolator"
+ android:propertyName="trimPathOffset"
+ android:repeatCount="-1"
+ android:valueFrom="0"
+ android:valueTo="0.25"
+ android:valueType="floatType" />
+</set>
diff --git a/core/res/res/color/watch_switch_track_color_material.xml b/core/res/res/anim-watch/progress_indeterminate_rotation_material.xml
similarity index 60%
copy from core/res/res/color/watch_switch_track_color_material.xml
copy to core/res/res/anim-watch/progress_indeterminate_rotation_material.xml
index 402a536..63e66ec 100644
--- a/core/res/res/color/watch_switch_track_color_material.xml
+++ b/core/res/res/anim-watch/progress_indeterminate_rotation_material.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!--
+ Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,9 +14,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- Used for the background of switch track for watch switch preference. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false"
- android:alpha="0.4" android:color="?attr/colorPrimary" />
- <item android:color="?attr/colorPrimary" />
-</selector>
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@interpolator/progress_indeterminate_rotation_interpolator"
+ android:duration="16666"
+ android:propertyName="rotation"
+ android:repeatCount="-1"
+ android:valueFrom="0"
+ android:valueTo="720"
+ android:valueType="floatType" />
diff --git a/core/res/res/color/hint_foreground_material_dark.xml b/core/res/res/color/hint_foreground_material_dark.xml
index 77883d9..5cc9559 100644
--- a/core/res/res/color/hint_foreground_material_dark.xml
+++ b/core/res/res/color/hint_foreground_material_dark.xml
@@ -15,6 +15,10 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="true"
+ android:state_pressed="true"
+ android:alpha="@dimen/hint_pressed_alpha_material_dark"
+ android:color="@color/foreground_material_dark" />
<item android:alpha="@dimen/hint_alpha_material_dark"
android:color="@color/foreground_material_dark" />
</selector>
diff --git a/core/res/res/color/hint_foreground_material_light.xml b/core/res/res/color/hint_foreground_material_light.xml
index 99168fd..f7465e0 100644
--- a/core/res/res/color/hint_foreground_material_light.xml
+++ b/core/res/res/color/hint_foreground_material_light.xml
@@ -15,6 +15,10 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="true"
+ android:state_pressed="true"
+ android:alpha="@dimen/hint_pressed_alpha_material_light"
+ android:color="@color/foreground_material_light" />
<item android:alpha="@dimen/hint_alpha_material_light"
android:color="@color/foreground_material_light" />
</selector>
diff --git a/core/res/res/drawable-hdpi/watch_switch_track_mtrl.png b/core/res/res/drawable-hdpi/watch_switch_track_mtrl_alpha.png
similarity index 100%
rename from core/res/res/drawable-hdpi/watch_switch_track_mtrl.png
rename to core/res/res/drawable-hdpi/watch_switch_track_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/watch_switch_track_mtrl.png b/core/res/res/drawable-xhdpi/watch_switch_track_mtrl_alpha.png
similarity index 100%
rename from core/res/res/drawable-xhdpi/watch_switch_track_mtrl.png
rename to core/res/res/drawable-xhdpi/watch_switch_track_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/watch_switch_track_mtrl.png b/core/res/res/drawable-xxhdpi/watch_switch_track_mtrl_alpha.png
similarity index 100%
rename from core/res/res/drawable-xxhdpi/watch_switch_track_mtrl.png
rename to core/res/res/drawable-xxhdpi/watch_switch_track_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/color/watch_switch_track_color_material.xml b/core/res/res/drawable/watch_switch_track_material.xml
similarity index 73%
rename from core/res/res/color/watch_switch_track_color_material.xml
rename to core/res/res/drawable/watch_switch_track_material.xml
index 402a536..79e92a3 100644
--- a/core/res/res/color/watch_switch_track_color_material.xml
+++ b/core/res/res/drawable/watch_switch_track_material.xml
@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
-
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
http://www.apache.org/licenses/LICENSE-2.0
-
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- Used for the background of switch track for watch switch preference. -->
+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false"
- android:alpha="0.4" android:color="?attr/colorPrimary" />
- <item android:color="?attr/colorPrimary" />
+ <item android:state_enabled="false">
+ <bitmap android:alpha="0.1" android:src="@drawable/watch_switch_track_mtrl_alpha" />
+ </item>
+ <item>
+ <bitmap android:alpha="0.2" android:src="@drawable/watch_switch_track_mtrl_alpha" />
+ </item>
</selector>
diff --git a/core/res/res/color/watch_switch_track_color_material.xml b/core/res/res/interpolator-watch/progress_indeterminate_rotation_interpolator.xml
similarity index 60%
copy from core/res/res/color/watch_switch_track_color_material.xml
copy to core/res/res/interpolator-watch/progress_indeterminate_rotation_interpolator.xml
index 402a536..ed2655c 100644
--- a/core/res/res/color/watch_switch_track_color_material.xml
+++ b/core/res/res/interpolator-watch/progress_indeterminate_rotation_interpolator.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!--
+ Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,9 +14,5 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- Used for the background of switch track for watch switch preference. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false"
- android:alpha="0.4" android:color="?attr/colorPrimary" />
- <item android:color="?attr/colorPrimary" />
-</selector>
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 c 0.032,0.0 0.016,0.2 0.08,0.2 l 0.12,0.0 c 0.032,0.0 0.016,0.2 0.08,0.2 l 0.12,0.0 c 0.032,0.0 0.016,0.2 0.08,0.2 l 0.12,0.0 c 0.032,0.0 0.016,0.2 0.08,0.2 l 0.12,0.0 c 0.032,0.0 0.016,0.2 0.08,0.2 l 0.12,0.0 L 1.0,1.0" />
diff --git a/core/res/res/color/watch_switch_track_color_material.xml b/core/res/res/interpolator-watch/trim_end_interpolator.xml
similarity index 61%
copy from core/res/res/color/watch_switch_track_color_material.xml
copy to core/res/res/interpolator-watch/trim_end_interpolator.xml
index 402a536..f46d5e0 100644
--- a/core/res/res/color/watch_switch_track_color_material.xml
+++ b/core/res/res/interpolator-watch/trim_end_interpolator.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!--
+ Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,9 +14,5 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- Used for the background of switch track for watch switch preference. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false"
- android:alpha="0.4" android:color="?attr/colorPrimary" />
- <item android:color="?attr/colorPrimary" />
-</selector>
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 c 0.08,0.0 0.04,1.0 0.2,1.0 l 0.8,0.0 L 1.0,1.0" />
diff --git a/core/res/res/color/watch_switch_track_color_material.xml b/core/res/res/interpolator-watch/trim_offset_interpolator.xml
similarity index 61%
copy from core/res/res/color/watch_switch_track_color_material.xml
copy to core/res/res/interpolator-watch/trim_offset_interpolator.xml
index 402a536..d58672e 100644
--- a/core/res/res/color/watch_switch_track_color_material.xml
+++ b/core/res/res/interpolator-watch/trim_offset_interpolator.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!--
+ Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,9 +14,5 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- Used for the background of switch track for watch switch preference. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false"
- android:alpha="0.4" android:color="?attr/colorPrimary" />
- <item android:color="?attr/colorPrimary" />
-</selector>
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 l 0.4,1.0 l 0.6,0.0 L 1.0,1.0" />
diff --git a/core/res/res/color/watch_switch_track_color_material.xml b/core/res/res/interpolator-watch/trim_start_interpolator.xml
similarity index 61%
copy from core/res/res/color/watch_switch_track_color_material.xml
copy to core/res/res/interpolator-watch/trim_start_interpolator.xml
index 402a536..365609c 100644
--- a/core/res/res/color/watch_switch_track_color_material.xml
+++ b/core/res/res/interpolator-watch/trim_start_interpolator.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!--
+ Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,9 +14,5 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- Used for the background of switch track for watch switch preference. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false"
- android:alpha="0.4" android:color="?attr/colorPrimary" />
- <item android:color="?attr/colorPrimary" />
-</selector>
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 l 0.2,0.0 c 0.08,0.0 0.04,1.0 0.2,1.0 l 0.6,0.0 L 1.0,1.0" />
diff --git a/core/res/res/layout-watch/preference_widget_switch.xml b/core/res/res/layout-watch/preference_widget_switch.xml
index a1a845a..5881cf0 100644
--- a/core/res/res/layout-watch/preference_widget_switch.xml
+++ b/core/res/res/layout-watch/preference_widget_switch.xml
@@ -24,8 +24,7 @@
android:thumb="@drawable/watch_switch_thumb_material_anim"
android:thumbTint="@color/watch_switch_thumb_color_material"
android:thumbTintMode="multiply"
- android:track="@drawable/watch_switch_track_mtrl"
- android:trackTint="@color/watch_switch_track_color_material"
+ android:track="@drawable/watch_switch_track_material"
android:focusable="false"
android:clickable="false"
android:background="@null" />
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index c0afffd..ff35ce9 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Gekoppel aan <xliff:g id="SESSION">%s</xliff:g>. Tik om die netwerk te bestuur."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Altydaan-VPN koppel tans..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Altydaan-VPN gekoppel"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Altydaan-VPN is ontkoppel"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Altydaan-VPN-fout"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tik om op te stel"</string>
<string name="upload_file" msgid="2897957172366730416">"Kies lêer"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Geen lêer gekies nie"</string>
<string name="reset" msgid="2448168080964209908">"Stel terug"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Voer taalnaam in"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Voorgestel"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Alle tale"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Soek"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Werkmodus is AF"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Stel werkprofiel in staat om te werk, insluitend programme, agtergrondsinkronisering en verwante kenmerke."</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 206fe0a..6e4066d 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"ለ<xliff:g id="SESSION">%s</xliff:g> የተገናኘ። አውታረመረቡን ለማደራጀት ሁለቴ ንካ።"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"ሁልጊዜ የበራ VPN በመገናኘት ላይ…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"ሁልጊዜ የበራ VPN ተገናኝቷል"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"ሁልጊዜ የበራ የVPN ግንኙነት ተቋርጧል"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"ሁልጊዜ የበራ VPN ስህተት"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"ለማዋቀር መታ ያድርጉ"</string>
<string name="upload_file" msgid="2897957172366730416">"ፋይል ምረጥ"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ምንም ፋይል አልተመረጠም"</string>
<string name="reset" msgid="2448168080964209908">"ዳግም አስጀምር"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"የቋንቋ ስም ይተይቡ"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"የተጠቆሙ"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"ሁሉም ቋንቋዎች"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"ፈልግ"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"የሥራ ሁነታ ጠፍቷል"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"መተግበሪያዎችን፣ የበስተጀርባ ሥምረት እና ተዛማጅ ባሕሪዎችን ጨምሮ የሥራ መገለጫ እንዲሰራ ይፍቀዱ።"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 1541789..b3364d4 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1343,11 +1343,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"تم الاتصال بـ <xliff:g id="SESSION">%s</xliff:g>. انقر لإدارة الشبكة."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"جارٍ الاتصال بشبكة ظاهرية خاصة (VPN) دائمة التشغيل..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"تم الاتصال بشبكة ظاهرية خاصة (VPN) دائمة التشغيل"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"تم فصل الشبكة الظاهرية الخاصة (VPN) دائمة التشغيل"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"خطأ بشبكة ظاهرية خاصة (VPN) دائمة التشغيل"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"انقر للإعداد."</string>
<string name="upload_file" msgid="2897957172366730416">"اختيار ملف"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"لم يتم اختيار أي ملف"</string>
<string name="reset" msgid="2448168080964209908">"إعادة تعيين"</string>
@@ -1794,6 +1792,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"اكتب اسم اللغة"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"المقترحة"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"جميع اللغات"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"كل المناطق"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"البحث"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"وضع العمل معطَّل"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"السماح باستخدام الملف الشخصي للعمل، بما في ذلك التطبيقات ومزامنة الخلفية والميزات ذات الصلة."</string>
diff --git a/core/res/res/values-az-rAZ/strings.xml b/core/res/res/values-az-rAZ/strings.xml
index 9845b69..672a208 100644
--- a/core/res/res/values-az-rAZ/strings.xml
+++ b/core/res/res/values-az-rAZ/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> sessiyaya qoşulun. Şəbəkəni idarə etmək üçün tıklayın."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Həmişə aktiv VPN bağlanır..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN bağlantısı həmişə aktiv"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Həmişə aktiv VPN bağlantısı kəsildi"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Həmişə aktiv VPN xətası"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Quraşdırmaq üçün tıklayın"</string>
<string name="upload_file" msgid="2897957172366730416">"Fayl seçin"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Heç bir fayl seçilməyib"</string>
<string name="reset" msgid="2448168080964209908">"Sıfırlayın"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Dil adını daxil edin"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Təklif edilmiş"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Bütün dillər"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Bütün bölgələr"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Axtarın"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"İş rejimi DEAKTİVDİR"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Tətbiq, arxa fon sinxronizasiyası və digər əlaqədar xüsusiyyətlər daxil olmaqla iş profilinin fəaliyyətinə icazə verin."</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index e8d84d5..16851a6 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1268,11 +1268,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Povezano sa sesijom <xliff:g id="SESSION">%s</xliff:g>. Dodirnite da biste upravljali mrežom."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Povezivanje stalno uključenog VPN-a..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Stalno uključeni VPN je povezan"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Veza sa stalno uključenim VPN-om je prekinuta"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Greška stalno uključenog VPN-a"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Dodirnite da biste podesili"</string>
<string name="upload_file" msgid="2897957172366730416">"Odaberi datoteku"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nije izabrana nijedna datoteka"</string>
<string name="reset" msgid="2448168080964209908">"Ponovo postavi"</string>
@@ -1686,6 +1684,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Unesite naziv jezika"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Predloženi"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Svi jezici"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Pretraži"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Režim za Work je ISKLJUČEN"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Dozvoljava profilu za Work da funkcioniše, uključujući aplikacije, sinhronizaciju u pozadini i srodne funkcije."</string>
diff --git a/core/res/res/values-be-rBY/strings.xml b/core/res/res/values-be-rBY/strings.xml
index 792726a..2cfca40 100644
--- a/core/res/res/values-be-rBY/strings.xml
+++ b/core/res/res/values-be-rBY/strings.xml
@@ -1293,11 +1293,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Падлучаны да <xliff:g id="SESSION">%s</xliff:g>. Націсніце, каб кiраваць сеткай."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Падключэнне заўсёды ўключанага VPN..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Заўсёды ўключаны i падключаны VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Заўсёды ўключаны VPN адключаны"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Памылка заўсёды ўключанага VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Дакраніцеся, каб наладзіць"</string>
<string name="upload_file" msgid="2897957172366730416">"Выберыце файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Файл не выбраны"</string>
<string name="reset" msgid="2448168080964209908">"Скінуць"</string>
@@ -1722,6 +1720,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Увядзіце назву мовы"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Прапанаваныя"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Усе мовы"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Усе рэгіёны"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Шукаць"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Рэжым працы АДКЛЮЧАНЫ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Дазволіць функцыянаванне працоўнага профілю, у тым ліку праграм, фонавай сінхранізацыі і звязаных з імі функцый."</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 38f5b93..15538ce 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Свързана с/ъс <xliff:g id="SESSION">%s</xliff:g>. Докоснете, за да управлявате мрежата."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Установява се връзка с винаги включената виртуална частна мрежа (VPN)…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Установена е връзка с винаги включената виртуална частна мрежа (VPN)"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Няма връзка с винаги включената виртуална частна мрежа (VPN)"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Грешка във винаги включената виртуална частна мрежа (VPN)"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Докоснете, за да настроите"</string>
<string name="upload_file" msgid="2897957172366730416">"Избор на файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Няма избран файл"</string>
<string name="reset" msgid="2448168080964209908">"Повторно задаване"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Въведете име на език"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Предложени"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Всички езици"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Всички региони"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Търсене"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Работният режим е ИЗКЛЮЧЕН"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Разрешаване на функционирането на служебния потребителски профил, включително приложенията, синхронизирането на заден план и свързаните функции."</string>
diff --git a/core/res/res/values-bn-rBD/strings.xml b/core/res/res/values-bn-rBD/strings.xml
index 8dc65fd..27287e4 100644
--- a/core/res/res/values-bn-rBD/strings.xml
+++ b/core/res/res/values-bn-rBD/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> তে সংযুক্ত হয়েছে৷ নেটওয়ার্ক পরিচালনা করতে আলতো চাপুন৷"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"সর্বদা-চালু VPN সংযুক্ত হচ্ছে..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"সর্বদা-চালু VPN সংযুক্ত হয়েছে"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"সর্বদা-চালু VPN এর সংযোগ বিচ্ছিন্ন হয়েছে"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"সর্বদা-চালু VPN ত্রুটি"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"সেট আপ করতে আলতো চাপুন"</string>
<string name="upload_file" msgid="2897957172366730416">"ফাইল বেছে নিন"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"কোনো ফাইল নির্বাচন করা হয়নি"</string>
<string name="reset" msgid="2448168080964209908">"পুনরায় সেট করুন"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"ভাষার নাম লিখুন"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"প্রস্তাবিত"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"সকল ভাষা"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"সমস্ত অঞ্চল"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"অনুসন্ধান করুন"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"কাজের মোড বন্ধ আছে"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"অ্যাপ্লিকেশান, পটভূমি সিঙ্ক এবং সম্পর্কিত বৈশিষ্ট্যগুলি সহ কর্মস্থলের প্রোফাইলটিকে কাজ করার অনুমতি দিন।"</string>
diff --git a/core/res/res/values-bs-rBA/strings.xml b/core/res/res/values-bs-rBA/strings.xml
index 7ea04e0..66e0791 100644
--- a/core/res/res/values-bs-rBA/strings.xml
+++ b/core/res/res/values-bs-rBA/strings.xml
@@ -1270,11 +1270,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Povezano sa sesijom <xliff:g id="SESSION">%s</xliff:g>. Dodirnite da upravljate mrežom."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Povezivanje na uvijek aktivni VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Povezan na uvijek aktivni VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Uvijek aktivni VPN nije povezan"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Greška u povezivanju na uvijek aktivni VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Dodirnite za postavke"</string>
<string name="upload_file" msgid="2897957172366730416">"Odabir fajla"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nije izabran nijedan fajl"</string>
<string name="reset" msgid="2448168080964209908">"Ponovno pokretanje"</string>
@@ -1688,6 +1686,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Ukucajte naziv jezika"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Predloženo"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Svi jezici"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Sve regije"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Pretraga"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Radni način rada je ISKLJUČEN"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Omogući radnom profilu da funkcionira, uključujući aplikacije, sinhronizaciju u pozadini i povezane funkcije."</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index b48ba58..8f0d9fa 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Connectat a <xliff:g id="SESSION">%s</xliff:g>. Pica per gestionar la xarxa."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"T\'estàs connectant a la VPN sempre activada…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Estàs connectat a la VPN sempre activada"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"La VPN sempre activada està desconnectada"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Error de la VPN sempre activada"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Toca per configurar"</string>
<string name="upload_file" msgid="2897957172366730416">"Trieu un fitxer"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"No s\'ha escollit cap fitxer"</string>
<string name="reset" msgid="2448168080964209908">"Restableix"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Nom de l\'idioma"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Suggerits"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Tots els idiomes"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Totes les regions"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Cerca"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Mode de feina desactivat"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Permet que el perfil professional funcioni, incloses les aplicacions, la sincronització en segon pla i les funcions relacionades."</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 4c24f23..439852a 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1293,11 +1293,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Připojeno k relaci <xliff:g id="SESSION">%s</xliff:g>. Klepnutím můžete síť spravovat."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Připojování k trvalé síti VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Je připojena trvalá síť VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Trvalá síť VPN je odpojena"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Chyba trvalé sítě VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Klepnutím přejděte do Nastavení"</string>
<string name="upload_file" msgid="2897957172366730416">"Zvolit soubor"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Není vybrán žádný soubor"</string>
<string name="reset" msgid="2448168080964209908">"Resetovat"</string>
@@ -1722,6 +1720,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Zadejte název jazyka"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Navrhované"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Všechny jazyky"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Vyhledávání"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Pracovní režim je VYPNUTÝ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Povolí fungování pracovního profilu, včetně aplikací, synchronizace na pozadí a souvisejících funkcí."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index d391cc3..5d1beba 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Forbundet til <xliff:g id="SESSION">%s</xliff:g>. Tryk for at administrere netværket."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Opretter forbindelse til altid aktiveret VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Always-on VPN er forbundet"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Forbindelsen til altid aktiveret VPN er afbrudt"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Fejl i altid aktiveret VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tryk for at konfigurere"</string>
<string name="upload_file" msgid="2897957172366730416">"Vælg fil"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
<string name="reset" msgid="2448168080964209908">"Nulstil"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Angiv sprogets navn"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Foreslået"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Alle sprog"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Alle områder"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Søg"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Arbejdstilstand er slået FRA"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Tillad, at arbejdsprofilen aktiveres, bl.a. i forbindelse med apps, baggrundssynkronisering og relaterede funktioner."</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 2f5e30d..407a397 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Verbunden mit <xliff:g id="SESSION">%s</xliff:g>. Zum Verwalten des Netzwerks tippen"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Verbindung zu durchgehend aktivem VPN wird hergestellt…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Mit durchgehend aktivem VPN verbunden"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Verbindung zu durchgehend aktivem VPN getrennt"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Durchgehend aktives VPN – Verbindungsfehler"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Zum Einrichten tippen"</string>
<string name="upload_file" msgid="2897957172366730416">"Datei auswählen"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Keine ausgewählt"</string>
<string name="reset" msgid="2448168080964209908">"Zurücksetzen"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Sprache eingeben"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Vorschläge"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Alle Sprachen"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Alle Regionen"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Suche"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Arbeitsmodus ist AUS"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Arbeitsprofil aktivieren, einschließlich Apps, Synchronisierung im Hintergrund und verknüpfter Funktionen."</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 34acbee..2a766e6 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Συνδέθηκε με <xliff:g id="SESSION">%s</xliff:g>. Πατήστε για να διαχειριστείτε το δίκτυο."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Σύνδεση πάντα ενεργοποιημένου VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Έχει συνδεθεί πάντα ενεργοποιημένο VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Το πάντα ενεργοποιημένο VPN αποσυνδέθηκε"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Σφάλμα πάντα ενεργοποιημένου VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Πατήστε για ρύθμιση"</string>
<string name="upload_file" msgid="2897957172366730416">"Επιλογή αρχείου"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Δεν επιλέχθηκε κανένα αρχείο."</string>
<string name="reset" msgid="2448168080964209908">"Επαναφορά"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Εισαγ. όνομα γλώσσας"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Προτεινόμενες"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Όλες οι γλώσσες"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Όλες οι περιοχές"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Αναζήτηση"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Λειτουργία εργασίας ΑΠΕΝΕΡΓ/ΝΗ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Να επιτρέπεται η λειτουργία του προφίλ εργασίας σας, συμπεριλαμβανομένων των εφαρμογών, του συγχρονισμού στο παρασκήνιο και των σχετικών λειτουργιών."</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 27a3ecb..1fe415e 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Connected to <xliff:g id="SESSION">%s</xliff:g>. Tap to manage the network."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Always-on VPN connecting…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Always-on VPN connected"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Always-on VPN disconnected"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Always-on VPN error"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tap to set up"</string>
<string name="upload_file" msgid="2897957172366730416">"Choose file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"No file chosen"</string>
<string name="reset" msgid="2448168080964209908">"Reset"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Type language name"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Suggested"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"All languages"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"All regions"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Search"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Work mode is OFF"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Allow work profile to function, including apps, background sync and related features."</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 27a3ecb..1fe415e 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Connected to <xliff:g id="SESSION">%s</xliff:g>. Tap to manage the network."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Always-on VPN connecting…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Always-on VPN connected"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Always-on VPN disconnected"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Always-on VPN error"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tap to set up"</string>
<string name="upload_file" msgid="2897957172366730416">"Choose file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"No file chosen"</string>
<string name="reset" msgid="2448168080964209908">"Reset"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Type language name"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Suggested"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"All languages"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"All regions"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Search"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Work mode is OFF"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Allow work profile to function, including apps, background sync and related features."</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 27a3ecb..1fe415e 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Connected to <xliff:g id="SESSION">%s</xliff:g>. Tap to manage the network."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Always-on VPN connecting…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Always-on VPN connected"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Always-on VPN disconnected"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Always-on VPN error"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tap to set up"</string>
<string name="upload_file" msgid="2897957172366730416">"Choose file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"No file chosen"</string>
<string name="reset" msgid="2448168080964209908">"Reset"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Type language name"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Suggested"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"All languages"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"All regions"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Search"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Work mode is OFF"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Allow work profile to function, including apps, background sync and related features."</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 85ef77c..dbefd6c 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Conectado a <xliff:g id="SESSION">%s</xliff:g>. Pulsa para gestionar la red."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Estableciendo conexión con la VPN siempre activada..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Se estableció conexión con la VPN siempre activada."</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Se desconectó la VPN siempre activada"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Se produjo un error al establecer conexión con la VPN siempre activada."</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Presiona para configurar"</string>
<string name="upload_file" msgid="2897957172366730416">"Elegir archivo"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"No se seleccionó un archivo."</string>
<string name="reset" msgid="2448168080964209908">"Restablecer"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Nombre del idioma"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Sugeridos"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Todos los idiomas"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Búsqueda"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Modo de trabajo DESACTIVADO"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Permite que se active el perfil de trabajo, incluidas las apps, la sincronización en segundo plano y las funciones relacionadas."</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 94b1961..c7d3881 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Conectado a <xliff:g id="SESSION">%s</xliff:g>. Toca para administrar la red."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Conectando VPN siempre activada…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN siempre activada conectada"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"VPN siempre activada desconectada"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Error de VPN siempre activada"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Toca para configurar"</string>
<string name="upload_file" msgid="2897957172366730416">"Seleccionar archivo"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Archivo no seleccionado"</string>
<string name="reset" msgid="2448168080964209908">"Restablecer"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Nombre de idioma"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Sugeridos"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Todos los idiomas"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Todas las regiones"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Buscar"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Modo de trabajo desactivado"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Permite que se utilice el perfil de trabajo, incluidas las aplicaciones, la sincronización en segundo plano y las funciones relacionadas."</string>
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index e7b7ab0..cd751a8a 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Ühendatud seansiga <xliff:g id="SESSION">%s</xliff:g>. Koputage võrgu haldamiseks"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Ühendamine alati sees VPN-iga …"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Ühendatud alati sees VPN-iga"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Alati sees VPN pole ühendatud"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Alati sees VPN-i viga"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Puudutage seadistamiseks"</string>
<string name="upload_file" msgid="2897957172366730416">"Valige fail"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ühtegi faili pole valitud"</string>
<string name="reset" msgid="2448168080964209908">"Lähtesta"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Sisestage keele nimi"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Soovitatud"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Kõik keeled"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Kõik piirkonnad"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Otsing"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Töörežiim on VÄLJA LÜLITATUD"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Lubatakse tööprofiili toimingud, sh rakendused, taustal sünkroonimine ja seotud funktsioonid."</string>
diff --git a/core/res/res/values-eu-rES/strings.xml b/core/res/res/values-eu-rES/strings.xml
index 7727635..f84dde3 100644
--- a/core/res/res/values-eu-rES/strings.xml
+++ b/core/res/res/values-eu-rES/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> saiora konektatuta. Sakatu sarea kudeatzeko."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Beti aktibatuta dagoen VPNa konektatzen…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Beti aktibatuta dagoen VPNa konektatu da"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Deskonektatu egin da beti aktibatuta dagoen VPN konexioa"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Beti aktibatuta dagoen VPN errorea"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Konfiguratzeko, sakatu hau"</string>
<string name="upload_file" msgid="2897957172366730416">"Aukeratu fitxategia"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ez da fitxategirik aukeratu"</string>
<string name="reset" msgid="2448168080964209908">"Berrezarri"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Adierazi hizkuntza"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Iradokitakoak"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Hizkuntza guztiak"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Bilaketa"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Desaktibatuta dago laneko modua"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Baimendu laneko profilak funtzionatzea, besteak beste, aplikazioak, atzeko planoko sinkronizazioa eta erlazionatutako eginbideak."</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 913bd73..140b599 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -924,9 +924,9 @@
<item quantity="one">در <xliff:g id="COUNT_1">%d</xliff:g> سال</item>
<item quantity="other">در <xliff:g id="COUNT_1">%d</xliff:g> سال</item>
</plurals>
- <string name="VideoView_error_title" msgid="3534509135438353077">"مشکل در ویدیو"</string>
- <string name="VideoView_error_text_invalid_progressive_playback" msgid="3186670335938670444">"متأسفیم، این ویدیو برای پخش جریانی با این دستگاه معتبر نیست."</string>
- <string name="VideoView_error_text_unknown" msgid="3450439155187810085">"پخش این ویدیو ممکن نیست."</string>
+ <string name="VideoView_error_title" msgid="3534509135438353077">"مشکل در ویدئو"</string>
+ <string name="VideoView_error_text_invalid_progressive_playback" msgid="3186670335938670444">"متأسفیم، این ویدئو برای پخش جریانی با این دستگاه معتبر نیست."</string>
+ <string name="VideoView_error_text_unknown" msgid="3450439155187810085">"پخش این ویدئو ممکن نیست."</string>
<string name="VideoView_error_button" msgid="2822238215100679592">"تأیید"</string>
<string name="relative_time" msgid="1818557177829411417">"<xliff:g id="DATE">%1$s</xliff:g>، <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="noon" msgid="7245353528818587908">"ظهر"</string>
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"به <xliff:g id="SESSION">%s</xliff:g> متصل شد. برای مدیریت شبکه ضربه بزنید."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"در حال اتصال VPN همیشه فعال…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN همیشه فعال متصل شد"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"ارتباط VPN همیشه روشن قطع شد"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"خطای VPN همیشه فعال"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"برای راهاندازی ضربه بزنید"</string>
<string name="upload_file" msgid="2897957172366730416">"انتخاب فایل"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"هیچ فایلی انتخاب نشد"</string>
<string name="reset" msgid="2448168080964209908">"بازنشانی"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"نام زبان را تایپ کنید"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"پیشنهادشده"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"همه زبانها"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"همه منطقهها"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"جستجو"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"حالت کاری خاموش است"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"به نمایه کاری اجازه فعالیت ( شامل استفاده از برنامهها، همگامسازی در پسزمینه و قابلیتهای مرتبط) داده شود."</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index a515724..9676cc2 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Yhdistetty: <xliff:g id="SESSION">%s</xliff:g>. Hallinnoi verkkoa napauttamalla."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Yhdistetään aina käytössä olevaan VPN-verkkoon..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Yhdistetty aina käytössä olevaan VPN-verkkoon"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Aina käytössä olevan VPN:n yhteys on katkaistu"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Aina käytössä oleva VPN: virhe"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Määritä koskettamalla."</string>
<string name="upload_file" msgid="2897957172366730416">"Valitse tiedosto"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ei valittua tiedostoa"</string>
<string name="reset" msgid="2448168080964209908">"Palauta"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Anna kielen nimi"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Ehdotukset"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Kaikki kielet"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Kaikki alueet"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Haku"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Työtila on pois käytöstä"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Sallii työprofiiliin toiminnan, esimerkiksi sovellukset ja taustasynkronoinnin."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 09162b2..f071ee2 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Connecté à <xliff:g id="SESSION">%s</xliff:g>. Appuyez ici pour gérer le réseau."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN permanent en cours de connexion…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN permanent connecté"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"RPV permanent déconnecté"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Erreur du VPN permanent"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Touchez pour configurer"</string>
<string name="upload_file" msgid="2897957172366730416">"Choisir un fichier"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Aucun fichier sélectionné"</string>
<string name="reset" msgid="2448168080964209908">"Réinitialiser"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Entrez la langue"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Suggestions"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Toutes les langues"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Rechercher"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Le mode Travail est désactivé"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Autoriser le fonctionnement du profil professionnel, y compris les applications, la synchronisation en arrière-plan et les fonctionnalités associées."</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index af4ec3c..df6024f5 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Connecté à <xliff:g id="SESSION">%s</xliff:g>. Appuyez ici pour gérer le réseau."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN permanent en cours de connexion…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN permanent connecté"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"VPN permanent déconnecté"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Erreur du VPN permanent"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Appuyer pour configurer"</string>
<string name="upload_file" msgid="2897957172366730416">"Sélectionner un fichier"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Aucun fichier sélectionné"</string>
<string name="reset" msgid="2448168080964209908">"Réinitialiser"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Saisissez la langue"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Recommandations"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Toutes les langues"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Rechercher"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Mode professionnel DÉSACTIVÉ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Autoriser le fonctionnement du profil professionnel, y compris les applications, la synchronisation en arrière-plan et les fonctionnalités associées."</string>
diff --git a/core/res/res/values-gl-rES/strings.xml b/core/res/res/values-gl-rES/strings.xml
index c676a09..42764bd 100644
--- a/core/res/res/values-gl-rES/strings.xml
+++ b/core/res/res/values-gl-rES/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Conectado a <xliff:g id="SESSION">%s</xliff:g>. Toca aquí para xestionar a rede."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN sempre activada conectándose..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sempre activada conectada"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Desconectouse a VPN sempre activada"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Erro na VPN sempre activada"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tocar para configurar"</string>
<string name="upload_file" msgid="2897957172366730416">"Escoller un ficheiro"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Non se seleccionou ningún ficheiro"</string>
<string name="reset" msgid="2448168080964209908">"Restablecer"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Nome do idioma"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Suxeridos"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Todos os idiomas"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Buscar"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Modo de traballo DESACTIVADO"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Permite que funcione o perfil de traballo, incluídas as aplicacións, a sincronización en segundo plano e as funcións relacionadas."</string>
diff --git a/core/res/res/values-gu-rIN/strings.xml b/core/res/res/values-gu-rIN/strings.xml
index 482d115..632cfae 100644
--- a/core/res/res/values-gu-rIN/strings.xml
+++ b/core/res/res/values-gu-rIN/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> થી કનેક્ટ થયાં. નેટવર્કને સંચાલિત કરવા માટે ટૅપ કરો."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"હંમેશા-ચાલુ VPN કનેક્ટ થઈ રહ્યું છે…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"હંમેશા-ચાલુ VPN કનેક્ટ થયું"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"હંમેશાં-ચાલુ VPN ડિસ્કનેક્ટ થયું"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"હંમેશાં ચાલુ VPN ભૂલ"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"સેટ કરવા માટે ટૅપ કરો"</string>
<string name="upload_file" msgid="2897957172366730416">"ફાઇલ પસંદ કરો"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"કોઈ ફાઇલ પસંદ કરેલી નથી"</string>
<string name="reset" msgid="2448168080964209908">"ફરીથી સેટ કરો"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"ભાષાનું નામ ટાઇપ કરો"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"સૂચવેલા"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"બધી ભાષાઓ"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"તમામ પ્રદેશ"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"શોધ"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"કાર્ય મોડ બંધ છે"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"કાર્ય પ્રોફાઇલને ઍપ્લિકેશનો, પૃષ્ઠભૂમિ સમન્વયન અને સંબંધિત સુવિધાઓ સહિતનું કાર્ય કરવાની મંજૂરી આપો."</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 239e7df..896565e 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> से कनेक्ट किया गया. नेटवर्क प्रबंधित करने के लिए टैप करें."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"हमेशा-चालू VPN कनेक्ट हो रहा है…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"हमेशा-चालू VPN कनेक्ट है"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"हमेशा-चालू VPN डिस्कनेक्ट है"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"हमेशा-चालू VPN त्रुटि"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"सेट करने के लिए टैप करें"</string>
<string name="upload_file" msgid="2897957172366730416">"फ़ाइल चुनें"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"कोई फ़ाइल चुनी नहीं गई"</string>
<string name="reset" msgid="2448168080964209908">"रीसेट करें"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"भाषा का नाम लिखें"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"सुझाए गए"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"सभी भाषाएं"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"सभी क्षेत्र"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"खोजें"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"कार्य मोड बंद है"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"ऐप्स, पृष्ठभूमि समन्वयन और संबंधित सुविधाओं सहित कार्य प्रोफ़ाइल को काम करने की अनुमति दें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 8699115..5934956 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1268,11 +1268,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Povezan sa sesijom <xliff:g id="SESSION">%s</xliff:g>. Dotaknite za upravljanje mrežom."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Povezivanje s uvijek uključenom VPN mrežom…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Povezan s uvijek uključenom VPN mrežom"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Prekinuta je veza s uvijek uključenom VPN mrežom"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Pogreška uvijek uključene VPN mreže"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Dodirnite za postavljanje"</string>
<string name="upload_file" msgid="2897957172366730416">"Odaberite datoteku"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nema odabranih datoteka"</string>
<string name="reset" msgid="2448168080964209908">"Ponovo postavi"</string>
@@ -1686,6 +1684,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Unesite naziv jezika"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Predloženo"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Svi jezici"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Sve regije"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Pretraži"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Radni je način ISKLJUČEN"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Omogućuje radnom profilu da funkcionira, uključujući aplikacije, sinkronizaciju u pozadini i povezane značajke."</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 3557fd5..75a7760 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Csatlakozva ide: <xliff:g id="SESSION">%s</xliff:g>. Érintse meg a hálózat kezeléséhez."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Csatlakozás a mindig bekapcsolt VPN-hez..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Csatlakozva a mindig bekapcsolt VPN-hez"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Kapcsolat bontva a mindig bekapcsolt VPN-nel"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Hiba a mindig bekapcsolt VPN-nel"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Koppintson ide a beállításhoz"</string>
<string name="upload_file" msgid="2897957172366730416">"Fájl kiválasztása"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nincs fájl kiválasztva"</string>
<string name="reset" msgid="2448168080964209908">"Alaphelyzet"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Adja meg a nyelvet"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Javasolt"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Minden nyelv"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Minden régió"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Keresés"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"A munka mód KI van kapcsolva"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Munkaprofil használatának engedélyezése, beleértve az alkalmazásokat, a háttérben való szinkronizálást és a kapcsolódó funkciókat."</string>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index b584b6c..edd5226 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Կապակացված է <xliff:g id="SESSION">%s</xliff:g>-ին: Սեղմեք` ցանցը կառավարելու համար:"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Միշտ-միացված VPN-ը կապվում է..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Միշտ-առցանց VPN-ը կապակցված է"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"«Միշտ միացված VPN»-ն անջատված է"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"VPN սխալը միշտ միացված"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Հպեք՝ կարգավորելու համար"</string>
<string name="upload_file" msgid="2897957172366730416">"Ընտրել ֆայլը"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ոչ մի ֆայլ չի ընտրված"</string>
<string name="reset" msgid="2448168080964209908">"Վերակայել"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Մուտքագրեք լեզուն"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Առաջարկներ"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Բոլոր լեզուները"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Բոլոր տարածաշրջանները"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Որոնում"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Աշխատանքային ռեժիմն ԱՆՋԱՏՎԱԾ Է"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Միացնել աշխատանքային պրոֆիլը՝ հավելվածները, ֆոնային համաժամեցումը և առնչվող գործառույթները"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 300d122..0715686 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Tersambung ke <xliff:g id="SESSION">%s</xliff:g>. Ketuk untuk mengelola jaringan."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Menyambungkan VPN selalu aktif..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN selalu aktif tersambung"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"VPN selalu aktif terputus"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Kesalahan VPN selalu aktif"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Ketuk untuk menyiapkan"</string>
<string name="upload_file" msgid="2897957172366730416">"Pilih file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Tidak ada file yang dipilih"</string>
<string name="reset" msgid="2448168080964209908">"Setel ulang"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Ketik nama bahasa"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Disarankan"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Semua bahasa"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Semua wilayah"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Telusuri"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Mode kerja NONAKTIF"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Izinkan profil kerja berfungsi, termasuk aplikasi, sinkronisasi latar belakang, dan fitur terkait."</string>
diff --git a/core/res/res/values-is-rIS/strings.xml b/core/res/res/values-is-rIS/strings.xml
index c7ea619..89104ec 100644
--- a/core/res/res/values-is-rIS/strings.xml
+++ b/core/res/res/values-is-rIS/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Tengt við <xliff:g id="SESSION">%s</xliff:g>. Ýttu til að hafa umsjón með netinu."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Sívirkt VPN tengist…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Sívirkt VPN tengt"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Sívirkt VPN aftengt"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Villa í sívirku VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Ýttu til að setja upp"</string>
<string name="upload_file" msgid="2897957172366730416">"Velja skrá"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Engin skrá valin"</string>
<string name="reset" msgid="2448168080964209908">"Endurstilla"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Sláðu inn heiti tungumáls"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Tillögur"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Öll tungumál"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Öll svæði"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Leita"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Slökkt á vinnusniði"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Leyfa virkni vinnusniðs, m.a. forrita, samstillingar í bakgrunni og tengdra eiginleika."</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 83f91ea..b132b2f 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Collegata a <xliff:g id="SESSION">%s</xliff:g>. Tocca per gestire la rete."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Connessione a VPN sempre attiva…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sempre attiva connessa"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"VPN sempre attiva disconnessa"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Errore VPN sempre attiva"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tocca per configurare"</string>
<string name="upload_file" msgid="2897957172366730416">"Scegli file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nessun file è stato scelto"</string>
<string name="reset" msgid="2448168080964209908">"Reimposta"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Digita nome lingua"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Suggerite"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Tutte le lingue"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Tutte le aree geografiche"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Cerca"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Modalità Lavoro DISATTIVATA"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Attiva il profilo di lavoro, incluse app, sincronizzazione in background e funzioni correlate."</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index df65070..aa68af1a 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1293,11 +1293,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"מחובר אל <xliff:g id="SESSION">%s</xliff:g>. הקש כדי לנהל את הרשת."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"ה-VPN שמופעל תמיד, מתחבר..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"ה-VPN שפועל תמיד, מחובר"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"חיבור תמידי ל-VPN מנותק"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"שגיאת VPN שמופעל תמיד"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"הקש כדי להגדיר"</string>
<string name="upload_file" msgid="2897957172366730416">"בחר קובץ"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"לא נבחר קובץ"</string>
<string name="reset" msgid="2448168080964209908">"איפוס"</string>
@@ -1722,6 +1720,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"הקלד שם שפה"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"הצעות"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"כל השפות"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"כל האזורים"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"חיפוש"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"מצב העבודה כבוי"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"אפשר לפרופיל העבודה לפעול, כולל אפליקציות, סנכרון ברקע ותכונות קשורות."</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index f331a4e..0b682bc 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g>に接続しました。ネットワークを管理するにはタップしてください。"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPNに常時接続しています…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPNに常時接続しました"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"常時接続 VPN の接続を解除しました"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"常時接続VPNのエラー"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"設定するにはタップします"</string>
<string name="upload_file" msgid="2897957172366730416">"ファイルを選択"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ファイルが選択されていません"</string>
<string name="reset" msgid="2448168080964209908">"リセット"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"言語名を入力"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"言語の候補"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"すべての言語"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"すべての地域"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"検索"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Work モード OFF"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"仕事用プロファイルで、アプリ、バックグラウンド同期などの関連機能の使用を許可します。"</string>
diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index d9dffef..28574b0 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"მიერთებულია <xliff:g id="SESSION">%s</xliff:g>-ზე. შეეხეთ ქსელის სამართავად."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"მიმდინარეობს მუდმივად ჩართული VPN-ის მიერთება…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"მუდმივად ჩართული VPN-ის მიერთებულია"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"მუდმივად ჩართული VPN გათიშულია"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"შეცდომა მუდამ VPN-ზე"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"შეეხეთ დასაყენებლად"</string>
<string name="upload_file" msgid="2897957172366730416">"ფაილის არჩევა"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ფაილი არჩეული არ არის"</string>
<string name="reset" msgid="2448168080964209908">"საწყისზე დაბრუნება"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"აკრიფეთ ენის სახელი"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"რეკომენდებული"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"ყველა ენა"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"ყველა რეგიონი"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"ძიება"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"სამსახურის რეჟიმი გამორთულია"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"სამსახურის პროფილის მუშაობის დაშვება, მათ შორის, აპების, ფონური სინქრონიზაციის და დაკავშირებული ფუნქციების."</string>
diff --git a/core/res/res/values-kk-rKZ/strings.xml b/core/res/res/values-kk-rKZ/strings.xml
index 3f72e2a..1827c1c 100644
--- a/core/res/res/values-kk-rKZ/strings.xml
+++ b/core/res/res/values-kk-rKZ/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> жүйесіне жалғанған. Желіні басқару үшін түріңіз."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Әрқашан қосылған ВЖЖ жалғануда…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Әрқашан қосылған ВЖЖ жалғанған"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Әрқашан қосулы VPN желісі ажыратылды"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Әрқашан қосылған ВЖЖ қателігі"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Реттеу үшін түртіңіз"</string>
<string name="upload_file" msgid="2897957172366730416">"Файлды таңдау"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ешқандай файл таңдалмаған"</string>
<string name="reset" msgid="2448168080964209908">"Қайта реттеу"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Тіл атауын теріңіз"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Ұсынылған"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Барлық тілдер"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Барлық аймақтар"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Іздеу"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Жұмыс режимі ӨШІРУЛІ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Жұмыс профиліне, соның ішінде, қолданбаларға, фондық синхрондауға және қатысты мүмкіндіктерге жұмыс істеуге рұқсат ету."</string>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index 3dafa2a..6108973 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -1245,11 +1245,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"បានភ្ជាប់ទៅ <xliff:g id="SESSION">%s</xliff:g> ។ ប៉ះ ដើម្បីគ្រប់គ្រងបណ្ដាញ។"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"បើកការតភ្ជាប់ VPN ជានិច្ច..។"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"ភ្ជាប់ VPN ជានិច្ច"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"បានផ្តាច់ VPN ដែលបើកជានិច្ច"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"បើកកំហុស VPN ជានិច្ច"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"ប៉ះដើម្បីដំឡើង"</string>
<string name="upload_file" msgid="2897957172366730416">"ជ្រើសឯកសារ"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"គ្មានឯកសារបានជ្រើស"</string>
<string name="reset" msgid="2448168080964209908">"កំណត់ឡើងវិញ"</string>
@@ -1652,6 +1650,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"វាយបញ្ចូលឈ្មោះភាសា"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"បានស្នើ"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"ភាសាទាំងអស់"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"ស្វែងរក"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"របៀបការងារបានបិទ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"អនុញ្ញាតឲ្យប្រវត្តិរូបការងារដំណើរការ ដោយរាប់បញ្ចូលទាំងកម្មវិធី ការធ្វើសមកាលកម្មផ្ទៃខាងក្រោយ និងលក្ខណៈពិសេសដែលពាក់ព័ន្ធ។"</string>
diff --git a/core/res/res/values-kn-rIN/strings.xml b/core/res/res/values-kn-rIN/strings.xml
index 545e42f..3868529 100644
--- a/core/res/res/values-kn-rIN/strings.xml
+++ b/core/res/res/values-kn-rIN/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> ಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ. ನೆಟ್ವರ್ಕ್ ನಿರ್ವಹಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"ಯಾವಾಗಲೂ-ಆನ್ VPN ಸಂಪರ್ಕಗೊಳ್ಳುತ್ತಿದೆ…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"ಯಾವಾಗಲೂ-ಆನ್ VPN ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"ಯಾವಾಗಲೂ-ಆನ್ VPN ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"ಯಾವಾಗಲೂ-ಆನ್ VPN ದೋಷ"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"ಹೊಂದಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="upload_file" msgid="2897957172366730416">"ಫೈಲ್ ಆಯ್ಕೆಮಾಡು"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ಯಾವುದೇ ಫೈಲ್ ಆಯ್ಕೆ ಮಾಡಿಲ್ಲ"</string>
<string name="reset" msgid="2448168080964209908">"ಮರುಹೊಂದಿಸು"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"ಭಾಷೆ ಹೆಸರನ್ನು ಟೈಪ್ ಮಾಡಿ"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"ಸೂಚಿತ ಭಾಷೆ"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"ಎಲ್ಲಾ ಭಾಷೆಗಳು"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"ಎಲ್ಲಾ ಪ್ರದೇಶಗಳು"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"ಹುಡುಕು"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"ಕೆಲಸದ ಮೋಡ್ ಆಫ್ ಆಗಿದೆ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"ಅಪ್ಲಿಕೇಶನ್ಗಳು, ಹಿನ್ನೆಲೆ ಸಿಂಕ್ ಮತ್ತು ಇತರ ಸಂಬಂಧಿತ ವೈಶಿಷ್ಟ್ಯಗಳು ಸೇರಿದಂತೆ ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್ ಕಾರ್ಯನಿರ್ವಹಿಸಲು ಅನುಮತಿಸಿ."</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index dc1da74..b9abc66 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g>에 연결되어 있습니다. 네트워크를 관리하려면 누르세요."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"연결 유지 VPN에 연결하는 중…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"연결 유지 VPN에 연결됨"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"연결 유지 VPN 연결 해제됨"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"연결 유지 VPN 오류"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"탭하여 설정"</string>
<string name="upload_file" msgid="2897957172366730416">"파일 선택"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"파일을 선택하지 않았습니다."</string>
<string name="reset" msgid="2448168080964209908">"초기화"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"언어 이름 입력"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"추천"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"모든 언어"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"검색"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"직장 모드가 사용 중지됨"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"앱, 백그라운드 동기화 및 관련 기능을 포함한 직장 프로필이 작동하도록 허용"</string>
diff --git a/core/res/res/values-ky-rKG/strings.xml b/core/res/res/values-ky-rKG/strings.xml
index c1e415e..0cf149a 100644
--- a/core/res/res/values-ky-rKG/strings.xml
+++ b/core/res/res/values-ky-rKG/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> сеансына туташуу ишке ашты. Желенин параметрлерин өзгөртүү үчүн бул жерди басыңыз."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Дайым иштеген VPN туташууда…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Дайым иштеген VPN туташтырылды"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Дайым иштеген VPN ажыратылды"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Дайым иштеген VPN\'де ката кетти"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Жөндөө үчүн таптаңыз"</string>
<string name="upload_file" msgid="2897957172366730416">"Файл тандоо"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Эч файл тандалган жок"</string>
<string name="reset" msgid="2448168080964209908">"Баштапкы абалга келтирүү"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Тилди киргизиңиз"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Сунушталган"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Бардык тилдер"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Издөө"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Жумуш режими ӨЧҮРҮЛГӨН"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Жумуш профилин, ошондой эле колдонмолорду, фондо шайкештирүү жана ага байланыштуу функцияларды иштетиңиз."</string>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index a6edcb0..35b2805 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"ເຊື່ອມຕໍ່ກັບ <xliff:g id="SESSION">%s</xliff:g> ແລ້ວ. ແຕະເພື່ອຈັດການເຄືອຂ່າຍ."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"ກຳລັງເຊື່ອມຕໍ່ Always-on VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"ເຊື່ອມຕໍ່ VPN ແບບເປີດຕະຫຼອດເວລາແລ້ວ"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"ຕັດການເຊື່ອມຕໍ່ VPN ແບບເປີດໃຊ້ຕະຫຼອດເວລາແລ້ວ"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"VPN ແບບເປີດຕະຫຼອດເກີດຄວາມຜິດພາດ"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"ແຕະເພື່ອຕັ້ງຄ່າ"</string>
<string name="upload_file" msgid="2897957172366730416">"ເລືອກໄຟລ໌"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ບໍ່ໄດ້ເລືອກໄຟລ໌ເທື່ອ"</string>
<string name="reset" msgid="2448168080964209908">"ຣີເຊັດ"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"ພິມຊື່ພາສາ"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"ແນະນຳ"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"ທຸກພາສາ"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"ຄົ້ນຫາ"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"ໂໝດບ່ອນເຮັດວຽກປິດຢູ່"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"ອະນຸຍາດໃຫ້ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກສາມາດນຳໃຊ້ໄດ້ ເຊິ່ງຮວມທັງແອັບ, ການຊິ້ງຂໍ້ມູນໃນພື້ນຫຼັງ ແລະ ຄຸນສົມບັດທີ່ກ່ຽວຂ້ອງ."</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index eb62a70..233e03e 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1293,11 +1293,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Prisijungta prie <xliff:g id="SESSION">%s</xliff:g>. Jei norite valdyti tinklą, palieskite."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Prisijungiama prie visada įjungto VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Prisijungta prie visada įjungto VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Visada įjungtas VPN atjungtas"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Visada įjungto VPN klaida"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Palieskite, kad nustatytumėte"</string>
<string name="upload_file" msgid="2897957172366730416">"Pasirinkti failą"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nepasirinktas joks failas"</string>
<string name="reset" msgid="2448168080964209908">"Atstatyti"</string>
@@ -1722,6 +1720,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Įveskite kalbos pav."</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Siūloma"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Visos kalbos"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Visi regionai"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Paieška"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Darbo režimas išjungtas"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Leisti veikti darbo profiliui, įskaitant programas, sinchronizavimą fone ir susijusias funkcijas."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index c920470..58d46fa 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1268,11 +1268,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Ir izveidots savienojums ar: <xliff:g id="SESSION">%s</xliff:g>. Pieskarieties, lai pārvaldītu tīklu."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Notiek savienojuma izveide ar vienmēr ieslēgtu VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Izveidots savienojums ar vienmēr ieslēgtu VPN."</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Vienmēr ieslēgts VPN ir atvienots"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Kļūda saistībā ar vienmēr ieslēgtu VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Pieskarieties, lai iestatītu."</string>
<string name="upload_file" msgid="2897957172366730416">"Izvēlēties failu"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Neviens fails nav izvēlēts"</string>
<string name="reset" msgid="2448168080964209908">"Atiestatīt"</string>
@@ -1686,6 +1684,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Ierakstiet valodas nosaukumu"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Ieteiktās"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Visas valodas"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Visi reģioni"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Meklēt"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Darba režīms IZSLĒGTS"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Atļaujiet darboties darba profilam, tostarp lietotnēm, sinhronizācijai fonā un saistītajām funkcijām."</string>
diff --git a/core/res/res/values-mcc204-mnc04/config.xml b/core/res/res/values-mcc204-mnc04/config.xml
index 0f39e42..ddf0e9f 100755
--- a/core/res/res/values-mcc204-mnc04/config.xml
+++ b/core/res/res/values-mcc204-mnc04/config.xml
@@ -25,13 +25,6 @@
-->
<integer name="config_mobile_mtu">1358</integer>
- <!-- service number convert map in roaming network. -->
- <!-- [dialstring],[replacement][,optional gid] -->
- <string-array translatable="false" name="dial_string_replace">
- <item>"*611:+19085594899,BAE0000000000000"</item>
- <item>"*86:+1MDN,BAE0000000000000"</item>
- </string-array>
-
<!-- Flag indicating whether strict threshold is used, or lenient threshold is used,
when evaluating RSRP for LTE antenna bar display
0. Strict threshold
diff --git a/core/res/res/values-mcc310-mnc120/config.xml b/core/res/res/values-mcc310-mnc120/config.xml
index 4b61688..413c698 100644
--- a/core/res/res/values-mcc310-mnc120/config.xml
+++ b/core/res/res/values-mcc310-mnc120/config.xml
@@ -25,9 +25,6 @@
-->
<integer name="config_mobile_mtu">1422</integer>
- <!-- Sprint need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">70</integer>
-
<!-- If this value is true, The mms content-disposition field is supported correctly.
If false, Content-disposition fragments are ignored -->
<bool name="config_mms_content_disposition_support">false</bool>
diff --git a/core/res/res/values-mcc310-mnc410/config.xml b/core/res/res/values-mcc310-mnc410/config.xml
index cddd5e3..9accdf0 100644
--- a/core/res/res/values-mcc310-mnc410/config.xml
+++ b/core/res/res/values-mcc310-mnc410/config.xml
@@ -67,7 +67,4 @@
<item>"#8"</item>
<item>"#9"</item>
</string-array>
- <!-- Flag indicating whether radio is to be restarted on the error of
- PDP_FAIL_REGULAR_DEACTIVATION/0x24 -->
- <bool name="config_restart_radio_on_pdp_fail_regular_deactivation">true</bool>
</resources>
diff --git a/core/res/res/values-mcc311-mnc221/config.xml b/core/res/res/values-mcc311-mnc221/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc221/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc222/config.xml b/core/res/res/values-mcc311-mnc222/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc222/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc223/config.xml b/core/res/res/values-mcc311-mnc223/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc223/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc224/config.xml b/core/res/res/values-mcc311-mnc224/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc224/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc225/config.xml b/core/res/res/values-mcc311-mnc225/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc225/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc226/config.xml b/core/res/res/values-mcc311-mnc226/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc226/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc227/config.xml b/core/res/res/values-mcc311-mnc227/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc227/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc228/config.xml b/core/res/res/values-mcc311-mnc228/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc228/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc229/config.xml b/core/res/res/values-mcc311-mnc229/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc229/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc480/config.xml b/core/res/res/values-mcc311-mnc480/config.xml
index 4e17e98..714b312 100755
--- a/core/res/res/values-mcc311-mnc480/config.xml
+++ b/core/res/res/values-mcc311-mnc480/config.xml
@@ -52,11 +52,6 @@
<bool name="config_carrier_volte_provisioned">true</bool>
<bool name="config_auto_attach_data_on_creation">false</bool>
- <!-- service number convert map in roaming network. -->
- <string-array translatable="false" name="dial_string_replace">
- <item>"*611:+19085594899,"</item>
- <item>"*86:+1MDN,"</item>
- </string-array>
<!-- Flag indicating whether strict threshold is used, or lenient threshold is used,
when evaluating RSRP for LTE antenna bar display
@@ -70,11 +65,4 @@
</string-array>
<string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true</string>
- <!-- Network switching warnings use toasts. -->
- <integer translatable="false" name="config_networkNotifySwitchType">2</integer>
- <!-- Notify when switching from Wi-Fi to cellular data. -->
- <string-array translatable="false" name="config_networkNotifySwitches">
- <item>WIFI-CELLULAR</item>
- </string-array>
-
</resources>
diff --git a/core/res/res/values-mcc311-mnc490/config.xml b/core/res/res/values-mcc311-mnc490/config.xml
index d481c97..836abdf 100644
--- a/core/res/res/values-mcc311-mnc490/config.xml
+++ b/core/res/res/values-mcc311-mnc490/config.xml
@@ -21,9 +21,6 @@
for different hardware and product builds. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Sprint need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">70</integer>
-
<!-- An array of CDMA roaming indicators which means international roaming -->
<integer-array translatable="false" name="config_cdma_international_roaming_indicators" >
<item>2</item>
diff --git a/core/res/res/values-mcc311-mnc580/config.xml b/core/res/res/values-mcc311-mnc580/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc580/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc581/config.xml b/core/res/res/values-mcc311-mnc581/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc581/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc582/config.xml b/core/res/res/values-mcc311-mnc582/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc582/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc583/config.xml b/core/res/res/values-mcc311-mnc583/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc583/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc584/config.xml b/core/res/res/values-mcc311-mnc584/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc584/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc585/config.xml b/core/res/res/values-mcc311-mnc585/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc585/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc586/config.xml b/core/res/res/values-mcc311-mnc586/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc586/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc587/config.xml b/core/res/res/values-mcc311-mnc587/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc587/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc588/config.xml b/core/res/res/values-mcc311-mnc588/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc588/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc589/config.xml b/core/res/res/values-mcc311-mnc589/config.xml
deleted file mode 100644
index 811e9c7..0000000
--- a/core/res/res/values-mcc311-mnc589/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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 my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
-</resources>
diff --git a/core/res/res/values-mcc311-mnc870/config.xml b/core/res/res/values-mcc311-mnc870/config.xml
index 98cb72e..f6aed13 100644
--- a/core/res/res/values-mcc311-mnc870/config.xml
+++ b/core/res/res/values-mcc311-mnc870/config.xml
@@ -25,9 +25,6 @@
-->
<integer name="config_mobile_mtu">1422</integer>
- <!-- Sprint need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">70</integer>
-
<!-- An array of CDMA roaming indicators which means international roaming -->
<integer-array translatable="false" name="config_cdma_international_roaming_indicators" >
<item>2</item>
diff --git a/core/res/res/values-mcc312-mnc530/config.xml b/core/res/res/values-mcc312-mnc530/config.xml
index 98cb72e..f6aed13 100644
--- a/core/res/res/values-mcc312-mnc530/config.xml
+++ b/core/res/res/values-mcc312-mnc530/config.xml
@@ -25,9 +25,6 @@
-->
<integer name="config_mobile_mtu">1422</integer>
- <!-- Sprint need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">70</integer>
-
<!-- An array of CDMA roaming indicators which means international roaming -->
<integer-array translatable="false" name="config_cdma_international_roaming_indicators" >
<item>2</item>
diff --git a/core/res/res/values-mcc311-mnc220/config.xml b/core/res/res/values-mcc722-mnc36/config.xml
similarity index 69%
rename from core/res/res/values-mcc311-mnc220/config.xml
rename to core/res/res/values-mcc722-mnc36/config.xml
index 811e9c7..daf5373 100644
--- a/core/res/res/values-mcc311-mnc220/config.xml
+++ b/core/res/res/values-mcc722-mnc36/config.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
-** Copyright 2014, The Android Open Source Project
+** Copyright 2016, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
@@ -17,10 +17,9 @@
*/
-->
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- USC need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">300</integer>
+ <!-- Don't use roaming icon for considered operators -->
+ <string-array translatable="false" name="config_operatorConsideredNonRoaming">
+ <item>72234</item>
+ </string-array>
</resources>
diff --git a/core/res/res/values-mk-rMK/strings.xml b/core/res/res/values-mk-rMK/strings.xml
index 3b0729a..0573cc6 100644
--- a/core/res/res/values-mk-rMK/strings.xml
+++ b/core/res/res/values-mk-rMK/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Поврзани сте на <xliff:g id="SESSION">%s</xliff:g>. Допрете за да управувате со мрежата."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Поврзување со секогаш вклучена VPN..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Поврзани со секогаш вклучена VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Секогаш вклучената VPN е неповрзана"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Грешка на секогаш вклучена VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Допрете за да поставите"</string>
<string name="upload_file" msgid="2897957172366730416">"Избери датотека"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Не е избрана датотека"</string>
<string name="reset" msgid="2448168080964209908">"Ресетирај"</string>
@@ -1652,6 +1650,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Внеси име на јазик"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Предложени"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Сите јазици"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Сите региони"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Пребарај"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Режимот на работа е ИСКЛУЧЕН"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Дозволете работниот профил да функционира, вклучувајќи ги апликациите, синхронизирањето во заднина и други поврзани функции."</string>
diff --git a/core/res/res/values-ml-rIN/strings.xml b/core/res/res/values-ml-rIN/strings.xml
index 237f3e2..01549ff 100644
--- a/core/res/res/values-ml-rIN/strings.xml
+++ b/core/res/res/values-ml-rIN/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> എന്ന സെഷനിലേക്ക് കണക്റ്റുചെയ്തു. നെറ്റ്വർക്ക് മാനേജുചെയ്യാൻ ടാപ്പുചെയ്യുക."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"എല്ലായ്പ്പോഴും ഓണായിരിക്കുന്ന VPN കണക്റ്റുചെയ്യുന്നു…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"എല്ലായ്പ്പോഴും ഓണായിരിക്കുന്ന VPN കണക്റ്റുചെയ്തു"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"\'എല്ലായ്പ്പോഴും ഓണായിരിക്കുന്ന VPN\' വിച്ഛേദിച്ചു"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"എല്ലായ്പ്പോഴും ഓണായിരിക്കുന്ന VPN പിശക്"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"സജ്ജമാക്കാൻ ടാപ്പുചെയ്യുക"</string>
<string name="upload_file" msgid="2897957172366730416">"ഫയല് തിരഞ്ഞെടുക്കുക"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ഫയലൊന്നും തിരഞ്ഞെടുത്തില്ല"</string>
<string name="reset" msgid="2448168080964209908">"പുനഃസജ്ജമാക്കുക"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"ഭാഷയുടെ പേര് ടൈപ്പുചെയ്യുക"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"നിര്ദ്ദേശിച്ചത്"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"എല്ലാ ഭാഷകളും"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"തിരയുക"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"ഔദ്യോഗിക മോഡ് ഓഫാണ്"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"ആപ്സും, പശ്ചാത്തല സമന്വയവും ബന്ധപ്പെട്ട ഫീച്ചറുകളും ഉൾപ്പെടെ, ഔദ്യോഗിക പ്രൊഫൈലിനെ പ്രവർത്തിക്കാൻ അനുവദിക്കുക."</string>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index 1a05ebe..1f35690 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g>-д холбогдов. Сүлжээг удирдах бол товшино уу."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Байнгын VPN-д холбогдож байна..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Байнга VPN холбоотой"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Тогтмол асаалттай VPN салсан"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Байнгын VPN алдаа"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Тохируулахын тулд товшино уу"</string>
<string name="upload_file" msgid="2897957172366730416">"Файл сонгох"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Сонгосон файл байхгүй"</string>
<string name="reset" msgid="2448168080964209908">"Бүгдийг цэвэрлэх"</string>
@@ -1648,6 +1646,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Улсын хэлийг бичнэ үү"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Санал болгосон"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Бүх хэл"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Бүх бүс нутаг"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Хайх"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Ажлын горимыг УНТРААСАН байна"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Ажлын профайлд апп, дэвсгэр синхрончлол болон бусад холбоотой тохиргоог ажиллахыг зөвшөөрнө үү."</string>
diff --git a/core/res/res/values-mr-rIN/strings.xml b/core/res/res/values-mr-rIN/strings.xml
index 9334a6e..278d4ad 100644
--- a/core/res/res/values-mr-rIN/strings.xml
+++ b/core/res/res/values-mr-rIN/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> शी कनेक्ट केले. नेटवर्क व्यवस्थापित करण्यासाठी टॅप करा."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN कनेक्ट करणे नेहमी-चालू…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN कनेक्ट केलेले नेहमी-चालू"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"नेहमी-चालू असलेले VPN डिस्कनेक्ट केले"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"VPN त्रुटी नेहमी-चालू"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"सेट करण्यासाठी टॅप करा"</string>
<string name="upload_file" msgid="2897957172366730416">"फाईल निवडा"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"फाईल निवडली नाही"</string>
<string name="reset" msgid="2448168080964209908">"रीसेट करा"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"भाषा नाव टाइप करा"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"सूचित केलेले"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"सर्व भाषा"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"सर्व प्रदेश"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"शोध"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"कार्य मोड बंद आहे"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"कार्य प्रोफाइलला अॅप्स, पार्श्वभूमी संकालन आणि संबंधित वैशिष्ट्यांच्या समावेशासह कार्य करण्याची परवानगी द्या."</string>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index 2d2246e..268bca4 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Bersambung kepada <xliff:g id="SESSION">%s</xliff:g>. Ketik untuk mengurus rangkaian."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN sentiasa hidup sedang disambungkan..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sentiasa hidup telah disambungkan"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"VPN sentiasa hidup diputuskan sambungannya"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Ralat VPN sentiasa hidup"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Ketik untuk menyediakan"</string>
<string name="upload_file" msgid="2897957172366730416">"Pilih fail"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Tiada fail dipilih"</string>
<string name="reset" msgid="2448168080964209908">"Tetapkan semula"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Taipkan nama bahasa"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Dicadangkan"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Semua bahasa"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Cari"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Mod kerja DIMATIKAN"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Benarkan profil kerja berfungsi, termasuk apl, penyegerakan latar belakang dan ciri yang berkaitan."</string>
diff --git a/core/res/res/values-my-rMM/strings.xml b/core/res/res/values-my-rMM/strings.xml
index 78a4153..b6d42bc 100644
--- a/core/res/res/values-my-rMM/strings.xml
+++ b/core/res/res/values-my-rMM/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> သို့ ချိတ်ဆက်ထားသည်။ ကွန်ရက်ကို စီမံခန့်ခွဲရန် တို့ပါ။"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"အမြဲတမ်းဖွင့်ထား VPN ဆက်သွယ်နေစဉ်…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"အမြဲတမ်းဖွင့်ထား VPN ဆက်သွယ်မှုရှိ"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"အမြဲတမ်းဖွင့်ထားရသော VPN ပြတ်တောက်နေသည်"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"အမြဲတမ်းဖွင့်ထား VPN အမှား"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"ပြင်ဆင်သတ်မှတ်ရန် တို့ပါ"</string>
<string name="upload_file" msgid="2897957172366730416">"ဖိုင်ရွေးချယ်ရန်"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"မည်သည့်ဖိုင်ကိုမှမရွေးပါ"</string>
<string name="reset" msgid="2448168080964209908">"ပြန်လည်သတ်မှတ်ရန်"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"ဘာသာစကားအမည် ထည့်ပါ"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"အကြံပြုထားသော"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"ဘာသာစကားများအားလုံး"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"ဒေသအားလုံး"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"ရှာဖွေရန်"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"အလုပ်မုဒ် ပိတ်ထားသည်"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"အက်ပ်များ၊ နောက်ခံစင့်ခ်လုပ်ခြင်း၊ နှင့်သက်ဆိုင်သည့်အင်္ဂါရပ်များကို ဆောင်ရွက်ရန် အလုပ်ပရိုဖိုင်ကိုခွင့်ပြုပါ။"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index ef799ea..ffe3001 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Koblet til <xliff:g id="SESSION">%s</xliff:g>. Trykk for å administrere nettverket."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Alltid-på VPN kobler til ..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Alltid-på VPN er tilkoblet"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Alltid på-VPN er frakoblet"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Alltid-på VPN-feil"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Trykk for å konfigurere"</string>
<string name="upload_file" msgid="2897957172366730416">"Velg fil"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
<string name="reset" msgid="2448168080964209908">"Tilbakestill"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Skriv inn språknavn"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Foreslått"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Alle språk"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Alle områder"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Søk"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Jobbmodus er AV"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Slå på jobbprofilen, inkludert apper, synkronisering i bakgrunnen og relaterte funksjoner."</string>
diff --git a/core/res/res/values-ne-rNP/strings.xml b/core/res/res/values-ne-rNP/strings.xml
index e83449f..6b600a6 100644
--- a/core/res/res/values-ne-rNP/strings.xml
+++ b/core/res/res/values-ne-rNP/strings.xml
@@ -1249,11 +1249,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g>सँग जोडिएको। नेटवर्क प्रबन्ध गर्न हान्नुहोस्।"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN जडान सधै जोड्दै…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"सधैँ खुल्ला हुने VPN जोडिएको"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"सधैँ-सक्रिय VPN लाई विच्छेद गरियो"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"सधैँ भरि VPN त्रुटिमा"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"सेट अप गर्न ट्याप गर्नुहोस्"</string>
<string name="upload_file" msgid="2897957172366730416">"फाइल छान्नुहोस्"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"कुनै फाइल छानिएको छैन"</string>
<string name="reset" msgid="2448168080964209908">"पुनःसेट गर्नु"</string>
@@ -1656,6 +1654,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"भाषाको नाम टाइप गर्नुहोस्"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"सुझाव दिइयो"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"सम्पूर्ण भाषाहरू"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"सबै क्षेत्रहरू"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"खोज"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"कार्य मोड बन्द छ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"अनुप्रयोग, पृष्ठभूमि सिंक र सम्बन्धित विशेषताहरू सहित, कार्य प्रोफाइललाई कार्य गर्न अनुमति दिनुहोस्।"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 492647f..0583e8d 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Verbonden met <xliff:g id="SESSION">%s</xliff:g>. Tik om het netwerk te beheren."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Always-on VPN-verbinding maken…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Always-on VPN-verbinding"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Always-on VPN-verbinding ontkoppeld"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Fout met Always-on VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tik om in te stellen"</string>
<string name="upload_file" msgid="2897957172366730416">"Bestand kiezen"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Geen bestand geselecteerd"</string>
<string name="reset" msgid="2448168080964209908">"Resetten"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Typ een taalnaam"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Voorgesteld"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Alle talen"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Alle regio\'s"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Zoeken"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Werkmodus is UIT"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Functioneren van werkprofiel toestaan, waaronder apps, synchronisatie op de achtergrond en gerelateerde functies."</string>
diff --git a/core/res/res/values-pa-rIN/strings.xml b/core/res/res/values-pa-rIN/strings.xml
index 0b2af1e..9d8c3fc 100644
--- a/core/res/res/values-pa-rIN/strings.xml
+++ b/core/res/res/values-pa-rIN/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ। ਨੈੱਟਵਰਕ ਦੇ ਪ੍ਰਬੰਧਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"ਹਮੇਸ਼ਾਂ-ਚਾਲੂ VPN ਕਨੈਕਟ ਕਰ ਰਿਹਾ ਹੈ..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"ਹਮੇਸ਼ਾਂ-ਚਾਲੂ VPN ਕਨੈਕਟ ਕੀਤਾ"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"ਹਮੇਸ਼ਾ-ਚਾਲੂ VPN ਡਿਸਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"ਹਮੇਸ਼ਾਂ-ਚਾਲੂ VPN ਅਸ਼ੁੱਧੀ"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"ਸਥਾਪਤ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="upload_file" msgid="2897957172366730416">"ਫਾਈਲ ਚੁਣੋ"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ਕੋਈ ਫਾਈਲ ਨਹੀਂ ਚੁਣੀ ਗਈ"</string>
<string name="reset" msgid="2448168080964209908">"ਰੀਸੈੱਟ ਕਰੋ"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"ਭਾਸ਼ਾ ਨਾਮ ਟਾਈਪ ਕਰੋ"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"ਸੁਝਾਈਆਂ ਗਈਆਂ"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"ਸਾਰੀਆਂ ਭਾਸ਼ਾਵਾਂ"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"ਸਾਰੇ ਖੇਤਰ"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"ਖੋਜ"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"ਕੰਮ ਮੋਡ ਬੰਦ ਹੈ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"ਐਪਾਂ, ਬੈਕਗ੍ਰਾਊਂਡ ਸਮਕਾਲੀਕਰਨ, ਅਤੇ ਸਬੰਧਿਤ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਸ਼ਾਮਲ ਕਰਦੇ ਹੋਏ ਕੰਮ ਪ੍ਰੋਫਾਈਲ ਨੂੰ ਕੰਮ ਕਰਨ ਦੀ ਮਨਜ਼ੂਰੀ ਦਿਓ।"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 6d7d91e..3a26d64 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1293,11 +1293,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Nawiązano połączenie: <xliff:g id="SESSION">%s</xliff:g>. Dotknij, aby zarządzać siecią."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Łączę ze stałą siecią VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Połączono ze stałą siecią VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Rozłączono ze stałą siecią VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Błąd stałej sieci VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Kliknij, by skonfigurować"</string>
<string name="upload_file" msgid="2897957172366730416">"Wybierz plik"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nie wybrano pliku"</string>
<string name="reset" msgid="2448168080964209908">"Resetuj"</string>
@@ -1722,6 +1720,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Wpisz nazwę języka"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Sugerowane"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Wszystkie języki"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Wszystkie kraje"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Szukaj"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Tryb pracy jest WYŁĄCZONY"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Włącz profil do pracy, w tym aplikacje, synchronizację w tle i inne funkcje."</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 96ff854..eb4db3e 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Conectado a <xliff:g id="SESSION">%s</xliff:g>. Toque para gerenciar a rede."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN sempre ativa conectando..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sempre ativa conectada"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"VPN sempre ativa desconectada"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Erro na VPN sempre ativa"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Toque para configurar"</string>
<string name="upload_file" msgid="2897957172366730416">"Escolher arquivo"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nenhum arquivo escolhido"</string>
<string name="reset" msgid="2448168080964209908">"Redefinir"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Digitar nome do idioma"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Sugeridos"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Todos os idiomas"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Pesquisa"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Modo de trabalho DESATIVADO"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Permitir que o perfil de trabalho funcione, incluindo apps, sincronização em segundo plano e recursos relacionados"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 0fc2a09..71107d6 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Ligado a <xliff:g id="SESSION">%s</xliff:g>. Toque para gerir a rede."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"A ligar VPN sempre ativa..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sempre ativa ligada"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"VPN sempre ativa desligada"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Erro da VPN sempre ativa"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tocar para configurar"</string>
<string name="upload_file" msgid="2897957172366730416">"Escolher ficheiro"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Não foi selecionado nenhum ficheiro"</string>
<string name="reset" msgid="2448168080964209908">"Repor"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Intr. nome do idioma"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Sugeridos"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Todos os idiomas"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Todas as regiões"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Pesquisa"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Modo de trabalho DESATIVADO"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Permitir o funcionamento do perfil de trabalho, incluindo as aplicações, a sincronização em segundo plano e as funcionalidades relacionadas."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 96ff854..eb4db3e 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Conectado a <xliff:g id="SESSION">%s</xliff:g>. Toque para gerenciar a rede."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN sempre ativa conectando..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sempre ativa conectada"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"VPN sempre ativa desconectada"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Erro na VPN sempre ativa"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Toque para configurar"</string>
<string name="upload_file" msgid="2897957172366730416">"Escolher arquivo"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nenhum arquivo escolhido"</string>
<string name="reset" msgid="2448168080964209908">"Redefinir"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Digitar nome do idioma"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Sugeridos"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Todos os idiomas"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Pesquisa"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Modo de trabalho DESATIVADO"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Permitir que o perfil de trabalho funcione, incluindo apps, sincronização em segundo plano e recursos relacionados"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 2361e82..ce00c0e 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1268,11 +1268,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Conectat la <xliff:g id="SESSION">%s</xliff:g>. Apăsați pentru a gestiona rețeaua."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Se efectuează conectarea la rețeaua VPN activată permanent…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Conectat(ă) la rețeaua VPN activată permanent"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Rețeaua VPN activată permanent a fost deconectată"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Eroare de rețea VPN activată permanent"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Atingeți pentru a configura"</string>
<string name="upload_file" msgid="2897957172366730416">"Alegeți un fișier"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nu au fost găsite fișiere"</string>
<string name="reset" msgid="2448168080964209908">"Resetați"</string>
@@ -1686,6 +1684,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Numele limbii"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Sugerate"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Toate limbile"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Toate regiunile"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Căutați"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Modul de serviciu e DEZACTIVAT"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Permiteți profilului de serviciu să funcționeze, inclusiv aplicațiile, sincronizarea în fundal și funcțiile asociate."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index daf2eb8..377476a 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1293,11 +1293,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Подключено: \"<xliff:g id="SESSION">%s</xliff:g>\". Нажмите здесь, чтобы изменить настройки сети."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Подключение…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Подключено"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Отключено"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Ошибка"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Нажмите, чтобы настроить"</string>
<string name="upload_file" msgid="2897957172366730416">"Выбрать файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Не выбран файл"</string>
<string name="reset" msgid="2448168080964209908">"Сбросить"</string>
@@ -1722,6 +1720,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Введите язык"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Рекомендуемые"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Все языки"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Все регионы"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Поиск"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Рабочий режим отключен"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Включить рабочий профиль: приложения, фоновую синхронизацию и связанные функции."</string>
diff --git a/core/res/res/values-si-rLK/strings.xml b/core/res/res/values-si-rLK/strings.xml
index e14bb63..014cdac 100644
--- a/core/res/res/values-si-rLK/strings.xml
+++ b/core/res/res/values-si-rLK/strings.xml
@@ -1245,11 +1245,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> වෙත සම්බන්ධිතයි. ජාලය කළමනාකරණය කිරීමට තට්ටු කරන්න."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"සැමවිටම VPN සම්බන්ධ වෙමින්…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"නිරතුරුවම VPN සම්බන්ධ කර ඇත"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"සැමවිට ක්රියාත්මක VPN විසන්ධි කරන ලදී"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"සැමවිට සක්රිය VPN දෝෂය"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"පිහිටුවීමට තට්ටු කරන්න"</string>
<string name="upload_file" msgid="2897957172366730416">"ගොනුව තෝරන්න"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ගොනුවක් තෝරාගෙන නැත"</string>
<string name="reset" msgid="2448168080964209908">"යළි පිහිටුවන්න"</string>
@@ -1652,6 +1650,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"භාෂා නම ටයිප් කරන්න"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"යෝජිත"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"සියලු භාෂා"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"සියලු ප්රදේශ"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"සෙවීම"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"වැඩ ප්රකාරය ක්රියාවිරහිතයි"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"යෙදුම්, පසුබිම සමමුහුර්ත කිරීම, සහ සම්බන්ධිත විශේෂාංග ඇතුළුව, ක්රියා කිරීමට කාර්යාල පැතිකඩට ඉඩ දෙන්න"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 7dc5116..4adbe4b 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1293,11 +1293,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Pripojené k relácii <xliff:g id="SESSION">%s</xliff:g>. Po klepnutí môžete sieť spravovať."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Pripájanie k vždy zapnutej sieti VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Pripojenie k vždy zapnutej sieti VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Vždy zapnutá sieť VPN bola odpojená"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Chyba vždy zapnutej siete VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Klepnutím prejdete do Nastavení"</string>
<string name="upload_file" msgid="2897957172366730416">"Zvoliť súbor"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nie je vybratý žiadny súbor"</string>
<string name="reset" msgid="2448168080964209908">"Obnoviť"</string>
@@ -1722,6 +1720,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Zadajte názov jazyka"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Navrhované"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Všetky jazyky"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Všetky regióny"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Vyhľadávanie"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Pracovný režim je VYPNUTÝ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Povoľte fungovanie pracovného profilu vrátane aplikácií, synchronizácie na pozadí a súvisiacich funkcií."</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 5785167..45cd01d 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1293,11 +1293,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Povezan z mestom <xliff:g id="SESSION">%s</xliff:g>. Tapnite za upravljanje omrežja."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Povezovanje v stalno vklopljeno navidezno zasebno omrežje ..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Vzpostavljena povezava v stalno vklopljeno navidezno zasebno omrežje"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Povezava s stalno vklopljenim VPN-jem je prekinjena"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Napaka stalno vklopljenega navideznega zasebnega omrežja"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Dotaknite se, če želite nastaviti"</string>
<string name="upload_file" msgid="2897957172366730416">"Izberi datoteko"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nobena datoteka ni izbrana"</string>
<string name="reset" msgid="2448168080964209908">"Ponastavi"</string>
@@ -1722,6 +1720,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Vnesite ime jezika"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Predlagano"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Vsi jeziki"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Išči"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Delovni način IZKLOPLJEN"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Dovoljeno delovanje delovnega profila, vključno z aplikacijami, sinhronizacijo v ozadju in povezanimi funkcijami."</string>
diff --git a/core/res/res/values-sq-rAL/strings.xml b/core/res/res/values-sq-rAL/strings.xml
index df3b9e0..74060d7 100644
--- a/core/res/res/values-sq-rAL/strings.xml
+++ b/core/res/res/values-sq-rAL/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Lidhur me <xliff:g id="SESSION">%s</xliff:g>. Trokit për të menaxhuar rrjetin."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Po lidh VPN-në për aktivizim të përhershëm…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN e lidhur në mënyrë të përhershme"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Rrjeti VPN gjithmonë aktiv u shkëput"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Gabimi VPN-je për aktivizimin e përhershëm"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Trokit për ta konfiguruar"</string>
<string name="upload_file" msgid="2897957172366730416">"Zgjidh skedarin"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nuk u zgjodh asnjë skedar"</string>
<string name="reset" msgid="2448168080964209908">"Rivendos"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Shkruaj emrin e gjuhës"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Sugjeruar"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Të gjitha gjuhët"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Të gjitha rajonet"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Kërko"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Modaliteti i punës është JOAKTIV"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Lejoje profilin e punës të funksionojë, duke përfshirë aplikacionet, sinkronizimin në sfond dhe funksionet e lidhura."</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 790eb8b..f31a6a1 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1268,11 +1268,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Повезано са сесијом <xliff:g id="SESSION">%s</xliff:g>. Додирните да бисте управљали мрежом."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Повезивање стално укљученог VPN-а..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Стално укључени VPN је повезан"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Веза са стално укљученим VPN-ом је прекинута"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Грешка стално укљученог VPN-а"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Додирните да бисте подесили"</string>
<string name="upload_file" msgid="2897957172366730416">"Одабери датотеку"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Није изабрана ниједна датотека"</string>
<string name="reset" msgid="2448168080964209908">"Поново постави"</string>
@@ -1686,6 +1684,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Унесите назив језика"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Предложени"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Сви језици"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Претражи"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Режим за Work је ИСКЉУЧЕН"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Дозвољава профилу за Work да функционише, укључујући апликације, синхронизацију у позадини и сродне функције."</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 22d18a8..3b79391 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Ansluten till <xliff:g id="SESSION">%s</xliff:g>. Knacka lätt för att hantera nätverket."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Ansluter till Always-on VPN ..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Ansluten till Always-on VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Always-on VPN har kopplats från"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Fel på Always-on VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Tryck för att konfigurera"</string>
<string name="upload_file" msgid="2897957172366730416">"Välj fil"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil har valts"</string>
<string name="reset" msgid="2448168080964209908">"Återställ"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Ange språket"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Förslag"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Alla språk"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Alla regioner"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Söka"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Arbetsläget är inaktiverat"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Tillåt att jobbprofilen är aktiv, inklusive appar, bakgrundssynkronisering och andra tillhörande funktioner."</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 470a6cf..f9a077e 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1241,11 +1241,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Imeunganishwa kwa <xliff:g id="SESSION">%s</xliff:g>. Gonga ili kudhibiti mtandao"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Kila mara VPN iliyowashwa inaunganishwa…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Kila mara VPN iliyowashwa imeunganishwa"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Iwe imeondoa VPN kila wakati"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Kila mara kuna hitilafu ya VPN iliyowashwa"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Gonga ili uweke mipangilio"</string>
<string name="upload_file" msgid="2897957172366730416">"Chagua faili"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Hakuna faili iliyochaguliwa"</string>
<string name="reset" msgid="2448168080964209908">"Weka upya"</string>
@@ -1648,6 +1646,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Weka jina la lugha"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Inayopendekezwa"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Lugha zote"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Tafuta"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Hali ya kazi IMEZIMWA"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Ruhusu wasifu wa kazini utumike, ikiwa ni pamoja na programu, usawazishaji wa chini chini na vipengele vinavyohusiana."</string>
diff --git a/core/res/res/values-ta-rIN/strings.xml b/core/res/res/values-ta-rIN/strings.xml
index 1a7b944..c6386ac 100644
--- a/core/res/res/values-ta-rIN/strings.xml
+++ b/core/res/res/values-ta-rIN/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> உடன் இணைக்கப்பட்டது. நெட்வொர்க்கை நிர்வகிக்க, தட்டவும்."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"எப்போதும் இயங்கும் VPN உடன் இணைக்கிறது…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"எப்போதும் இயங்கும் VPN இணைக்கப்பட்டது"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"எப்போதும் இயங்கும் VPN துண்டிக்கப்பட்டது"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"எப்போதும் இயங்கும் VPN பிழை"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"அமைக்க, தட்டவும்"</string>
<string name="upload_file" msgid="2897957172366730416">"கோப்பைத் தேர்வுசெய்"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"எந்தக் கோப்பும் தேர்வுசெய்யப்படவில்லை"</string>
<string name="reset" msgid="2448168080964209908">"மீட்டமை"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"மொழி பெயரை உள்ளிடுக"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"பரிந்துரைகள்"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"எல்லா மொழிகளும்"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"எல்லா மண்டலங்களும்"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"தேடு"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"பணிப் பயன்முறை முடக்கப்பட்டது"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"செயல்பட, பணி சுயவிவரத்தை அனுமதி. இதில் பயன்பாடுகள், பின்னணி ஒத்திசைவு மற்றும் தொடர்புடைய அம்சங்கள் அடங்கும்."</string>
diff --git a/core/res/res/values-te-rIN/strings.xml b/core/res/res/values-te-rIN/strings.xml
index e2aa9d2..fb0c7a0 100644
--- a/core/res/res/values-te-rIN/strings.xml
+++ b/core/res/res/values-te-rIN/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g>కు కనెక్ట్ చేయబడింది. నెట్వర్క్ను నిర్వహించడానికి నొక్కండి."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"ఎల్లప్పుడూ-ఆన్లో ఉండే VPN కనెక్ట్ చేయబడుతోంది…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"ఎల్లప్పుడూ-ఆన్లో ఉండే VPN కనెక్ట్ చేయబడింది"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"ఎల్లప్పుడూ ఆన్లో ఉండే VPN డిస్కనెక్ట్ చేయబడింది"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"ఎల్లప్పుడూ-ఆన్లో ఉండే VPN లోపం"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"సెటప్ చేయడానికి నొక్కండి"</string>
<string name="upload_file" msgid="2897957172366730416">"ఫైల్ను ఎంచుకోండి"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ఫైల్ ఎంచుకోబడలేదు"</string>
<string name="reset" msgid="2448168080964209908">"రీసెట్ చేయి"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"భాష పేరును టైప్ చేయండి"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"సూచించినవి"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"అన్ని భాషలు"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"అన్ని ప్రాంతాలు"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"శోధించు"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"కార్యాలయ మోడ్ ఆఫ్ చేయబడింది"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"అనువర్తనాలు, నేపథ్య సమకాలీకరణ మరియు సంబంధిత లక్షణాలతో సహా కార్యాలయ ప్రొఫైల్ను పని చేయడానికి అనుమతించండి."</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 67b629a..f876216 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"เชื่อมต่อกับ <xliff:g id="SESSION">%s</xliff:g> แตะเพื่อจัดการเครือข่าย"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"กำลังเชื่อมต่อ VPN แบบเปิดตลอดเวลา…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"เชื่อมต่อ VPN แบบเปิดตลอดเวลาแล้ว"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"ยกเลิกการเชื่อมต่อ VPN แบบเปิดตลอดเวลาแล้ว"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"ข้อผิดพลาดของ VPN แบบเปิดตลอดเวลา"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"แตะเพื่อตั้งค่า"</string>
<string name="upload_file" msgid="2897957172366730416">"เลือกไฟล์"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ไม่ได้เลือกไฟล์ไว้"</string>
<string name="reset" msgid="2448168080964209908">"รีเซ็ต"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"พิมพ์ชื่อภาษา"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"แนะนำ"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"ทุกภาษา"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"ภูมิภาคทั้งหมด"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"ค้นหา"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"โหมดทำงานปิดอยู่"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"อนุญาตให้โปรไฟล์งานทำงานได้ ซึ่งรวมถึงแอป การซิงค์ในพื้นหลัง และคุณลักษณะอื่นที่เกี่ยวข้อง"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 162ef52..3397d8d 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Nakakonekta sa <xliff:g id="SESSION">%s</xliff:g>. Tapikin upang pamahalaan ang network."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Kumukonekta ang Always-on VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Nakakonekta ang Always-on VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Hindi nakakonekta ang palaging naka-on na VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Error sa Always-on VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"I-tap upang i-set up"</string>
<string name="upload_file" msgid="2897957172366730416">"Pumili ng file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Walang napiling file"</string>
<string name="reset" msgid="2448168080964209908">"I-reset"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"I-type ang wika"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Iminumungkahi"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Lahat ng wika"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Lahat ng rehiyon"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Maghanap"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"NAKA-OFF ang work mode"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Payagang gumana ang profile sa trabaho, kasama na ang mga app, pag-sync sa background at mga may kaugnayang feature."</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 93d3d23..a31fa47 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> oturumuna bağlı. Ağı yönetmek için hafifçe vurun."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Her zaman açık VPN\'ye bağlanılıyor…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Her zaman açık VPN\'ye bağlanıldı"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Her zaman açık VPN bağlantısı kesildi"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Her zaman açık VPN hatası"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Ayarlamak için dokunun"</string>
<string name="upload_file" msgid="2897957172366730416">"Dosya seç"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Seçili dosya yok"</string>
<string name="reset" msgid="2448168080964209908">"Sıfırla"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Dil adını yazın"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Önerilen"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Tüm diller"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Ara"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"İş modu KAPALI"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Uygulamalar, arka planda senkronizasyon ve ilgili özellikler dahil olmak üzere iş profilinin çalışmasına izin ver."</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 37b413c..3976558 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1293,11 +1293,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Під’єднано до <xliff:g id="SESSION">%s</xliff:g>. Торкніться, щоб керувати мережею."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Під’єднання до постійної мережі VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Під’єднано до постійної мережі VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Постійну мережу VPN від’єднано"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Помилка постійної мережі VPN"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Торкніться, щоб налаштувати"</string>
<string name="upload_file" msgid="2897957172366730416">"Виберіть файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Не вибрано файл"</string>
<string name="reset" msgid="2448168080964209908">"Віднов."</string>
@@ -1722,6 +1720,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Введіть назву мови"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Пропоновані"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Усі мови"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Усі регіони"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Пошук"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Робочий профіль ВИМКНЕНО"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Увімкнути робочий профіль, зокрема додатки, фонову синхронізацію та пов’язані функції."</string>
diff --git a/core/res/res/values-ur-rPK/strings.xml b/core/res/res/values-ur-rPK/strings.xml
index 8f3ceca..4bf8b52 100644
--- a/core/res/res/values-ur-rPK/strings.xml
+++ b/core/res/res/values-ur-rPK/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> سے منسلک ہے۔ نیٹ ورک کا نظم کرنے کیلئے تھپتھپائیں۔"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"ہمیشہ آن VPN مربوط ہو رہا ہے…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"ہمیشہ آن VPN مربوط ہوگیا"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"ہمیشہ آن VPN غیر منسلک ہو گیا"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"ہمیشہ آن VPN کی خرابی"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"سیٹ اپ کرنے کیلئے تھپتھپائیں"</string>
<string name="upload_file" msgid="2897957172366730416">"فائل منتخب کریں"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"کوئی فائل منتخب نہیں کی گئی"</string>
<string name="reset" msgid="2448168080964209908">"دوبارہ ترتیب دیں"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"زبان کا نام ٹائپ کریں"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"تجویز کردہ"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"سبھی زبانیں"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"تلاش"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"کام موڈ آف ہے"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"دفتری پروفائل کو کام کرنے دیں، بشمول ایپس، پس منظر کی مطابقت پذیری اور متعلقہ خصوصیات۔"</string>
diff --git a/core/res/res/values-uz-rUZ/strings.xml b/core/res/res/values-uz-rUZ/strings.xml
index cdd7979..ae7d547 100644
--- a/core/res/res/values-uz-rUZ/strings.xml
+++ b/core/res/res/values-uz-rUZ/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> ulandi. Tarmoq sozlamalarini o‘zgartirish uchun bu yerni bosing."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Ulanmoqda…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Ulandi"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Doimiy VPN o‘chirildi"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Xato"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Sozlash uchun bosing"</string>
<string name="upload_file" msgid="2897957172366730416">"Faylni tanlash"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Hech qanday fayl tanlanmadi"</string>
<string name="reset" msgid="2448168080964209908">"Asliga qaytarish"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"Til nomini kiriting"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Taklif etiladi"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Barcha tillar"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"Qidiruv"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Ish rejimi O‘CHIQ"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Ishchi profilini yoqish: ilovalar, fonda sinxronlash va bog‘liq funksiyalar."</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index e9419c4..b0df78b 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Đã kết nối với <xliff:g id="SESSION">%s</xliff:g>. Chạm để quản lý mạng."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Đang kết nối VPN luôn bật…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Đã kết nối VPN luôn bật"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Đã ngắt kết nối VPN luôn bật"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Lỗi VPN luôn bật"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Nhấn để thiết lập"</string>
<string name="upload_file" msgid="2897957172366730416">"Chọn tệp"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Không có tệp nào được chọn"</string>
<string name="reset" msgid="2448168080964209908">"Đặt lại"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Nhập tên ngôn ngữ"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Ðược đề xuất"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Tất cả ngôn ngữ"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Tất cả khu vực"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Tìm kiếm"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Chế độ làm việc đang TẮT"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Cho phép hồ sơ công việc hoạt động, bao gồm ứng dụng, đồng bộ hóa trong nền và các tính năng liên quan."</string>
diff --git a/core/res/res/color/watch_switch_track_color_material.xml b/core/res/res/values-watch/donottranslate.xml
similarity index 63%
copy from core/res/res/color/watch_switch_track_color_material.xml
copy to core/res/res/values-watch/donottranslate.xml
index 402a536..d247ff6 100644
--- a/core/res/res/color/watch_switch_track_color_material.xml
+++ b/core/res/res/values-watch/donottranslate.xml
@@ -13,9 +13,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- Used for the background of switch track for watch switch preference. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false"
- android:alpha="0.4" android:color="?attr/colorPrimary" />
- <item android:color="?attr/colorPrimary" />
-</selector>
+
+<resources>
+ <!-- DO NOT TRANSLATE Spans within this text are applied to style composing regions
+ within an EditText widget. The text content is ignored and not used.
+ Note: This is @color/material_deep_teal_200, cannot use @color references here. -->
+ <string name="candidates_style" translatable="false"><font color="#80cbc4">candidates</font></string>
+ </resources>
diff --git a/core/res/res/values-watch/styles_material.xml b/core/res/res/values-watch/styles_material.xml
index d09119f..8a080d9c 100644
--- a/core/res/res/values-watch/styles_material.xml
+++ b/core/res/res/values-watch/styles_material.xml
@@ -68,8 +68,6 @@
<item name="breakStrategy">balanced</item>
</style>
- <style name="Widget.Material.ButtonBar" parent="Widget.Material.BaseButtonBar" />
-
<style name="TextAppearance.Material.NumberPicker" parent="TextAppearance.Material.Body1">
<item name="textSize">@dimen/text_size_medium_material</item>
</style>
@@ -103,9 +101,4 @@
<item name="gravity">@integer/config_dialogTextGravity</item>
<item name="ellipsize">end</item>
</style>
-
- <!-- DO NOTE TRANSLATE Spans within this text are applied to style composing regions
- within an EditText widget. The text content is ignored and not used.
- Note: This is @color/material_deep_teal_200, cannot use @color references here. -->
- <string name="candidates_style"><font color="#80cbc4">candidates</font></string>
</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 5c239d8..c2bf810 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"已连接到<xliff:g id="SESSION">%s</xliff:g>。点按即可管理网络。"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"正在连接到始终开启的 VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"已连接到始终开启的 VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"始终开启的 VPN 已断开连接"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"始终开启的 VPN 出现错误"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"点按即可进行设置"</string>
<string name="upload_file" msgid="2897957172366730416">"选择文件"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"未选定任何文件"</string>
<string name="reset" msgid="2448168080964209908">"重置"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"输入语言名称"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"建议语言"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"所有语言"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"搜索"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"工作模式已关闭"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"启用工作资料,包括应用、后台同步和相关功能。"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 323668a..c8921fd 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"已連線至 <xliff:g id="SESSION">%s</xliff:g>,輕按一下即可管理網絡。"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"正在連線至永久連線的 VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"已連線至永久連線的 VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"永久連線的 VPN 已中斷"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"永久連線的 VPN 發生錯誤"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"輕按即可設定"</string>
<string name="upload_file" msgid="2897957172366730416">"選擇檔案"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"未選擇檔案"</string>
<string name="reset" msgid="2448168080964209908">"重設"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"輸入語言名稱"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"建議"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"所有語言"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"所有國家/地區"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"搜尋"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"工作模式已關閉"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"允許使用應用程式、背景同步及相關功能的工作設定檔。"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 6532f10..ea53582 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"已連線至 <xliff:g id="SESSION">%s</xliff:g>,輕觸一下即可管理網路。"</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"正在連線至永久連線的 VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"已連線至永久連線的 VPN"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"永久連線的 VPN 已中斷連線"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"永久連線的 VPN 發生錯誤"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"輕觸即可進行設定"</string>
<string name="upload_file" msgid="2897957172366730416">"選擇檔案"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"未選擇任何檔案"</string>
<string name="reset" msgid="2448168080964209908">"重設"</string>
@@ -1650,6 +1648,8 @@
<string name="search_language_hint" msgid="7042102592055108574">"請輸入語言名稱"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"建議語言"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"所有語言"</string>
+ <!-- no translation found for region_picker_section_all (8966316787153001779) -->
+ <skip />
<string name="locale_search_menu" msgid="2560710726687249178">"搜尋"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Work 模式已關閉"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"啟用 Work 設定檔,包括應用程式、背景同步處理和相關功能。"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index c97541a..22f2850 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1243,11 +1243,9 @@
<string name="vpn_text_long" msgid="4907843483284977618">"Ixhume ku-<xliff:g id="SESSION">%s</xliff:g>. Thepha ukuphatha inethiwekhi."</string>
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"I-VPN ehlala ikhanya iyaxhuma…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"I-VPN ehlala ikhanya ixhunyiwe"</string>
- <!-- no translation found for vpn_lockdown_disconnected (4532298952570796327) -->
- <skip />
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Njalo kuvuliwe i-VPN kunqamukile"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Iphutha le-VPN ehlala ikhanya"</string>
- <!-- no translation found for vpn_lockdown_config (5099330695245008680) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="5099330695245008680">"Thepha ukuze usethe"</string>
<string name="upload_file" msgid="2897957172366730416">"Khetha ifayela"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ayikho ifayela ekhethiwe"</string>
<string name="reset" msgid="2448168080964209908">"Setha kabusha"</string>
@@ -1650,6 +1648,7 @@
<string name="search_language_hint" msgid="7042102592055108574">"Thayipha igama lolimi"</string>
<string name="language_picker_section_suggested" msgid="8414489646861640885">"Okuphakanyisiwe"</string>
<string name="language_picker_section_all" msgid="3097279199511617537">"Zonke izilimi"</string>
+ <string name="region_picker_section_all" msgid="8966316787153001779">"Zonke izifunda"</string>
<string name="locale_search_menu" msgid="2560710726687249178">"Sesha"</string>
<string name="work_mode_off_title" msgid="8954725060677558855">"Imodi yomsebenzi IVALIWE"</string>
<string name="work_mode_off_message" msgid="3286169091278094476">"Vumela iphrofayela yomsebenzi ukuze isebenze, efaka izinhlelo zokusebenza, ukuvumelanisa kwangemuva, nezici ezisondelene."</string>
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index a864cf3..92426c6 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -61,7 +61,10 @@
<color name="secondary_text_default_material_dark">#b3ffffff</color>
<item name="hint_alpha_material_dark" format="float" type="dimen">0.50</item>
- <item name="hint_alpha_material_light" format="float" type="dimen">0.54</item>
+ <item name="hint_alpha_material_light" format="float" type="dimen">0.38</item>
+
+ <item name="hint_pressed_alpha_material_dark" format="float" type="dimen">0.70</item>
+ <item name="hint_pressed_alpha_material_light" format="float" type="dimen">0.54</item>
<item name="disabled_alpha_material_light" format="float" type="dimen">0.26</item>
<item name="disabled_alpha_material_dark" format="float" type="dimen">0.30</item>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 71e7977..e9e7d53 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -275,6 +275,11 @@
<string-array translatable="false" name="config_networkNotifySwitches">
</string-array>
+ <!-- Whether the device should automatically switch away from Wi-Fi networks that lose
+ Internet access. Actual device behaviour is controlled by
+ Settings.Global.NETWORK_AVOID_BAD_WIFI. This is the default value of that setting. -->
+ <integer translatable="false" name="config_networkAvoidBadWifi">1</integer>
+
<!-- List of regexpressions describing the interface (if any) that represent tetherable
USB interfaces. If the device doesn't want to support tethering over USB this should
be empty. An example would be "usb.*" -->
@@ -2261,6 +2266,9 @@
<item>SUPL_MODE=1</item>
</string-array>
+ <!-- Sprint need a 70 ms delay for 3way call -->
+ <integer name="config_cdma_3waycall_flash_delay">0</integer>
+
<!-- If there is no preload VM number in the sim card, carriers such as
Verizon require to load a default vm number from the configurantion.
Define config_default_vm_number for this purpose. And there are two
@@ -2276,9 +2284,6 @@
current sim's gid, the last one without gid will be picked -->
<string-array translatable="false" name="config_default_vm_number" />
- <!-- Sprint need a 70 ms delay for 3way call -->
- <integer name="config_cdma_3waycall_flash_delay">0</integer>
-
<!--SIM does not save, but the voice mail number to be changed. -->
<bool name="editable_voicemailnumber">false</bool>
@@ -2617,4 +2622,16 @@
<!-- Verizon requires any SMS that starts with //VZWVVM to be treated as a VVM SMS-->
<item>310004,310010,310012,310013,310590,310890,310910,311110,311270,311271,311272,311273,311274,311275,311276,311277,311278,311279,311280,311281,311282,311283,311284,311285,311286,311287,311288,311289,311390,311480,311481,311482,311483,311484,311485,311486,311487,311488,311489;^//VZWVVM.*</item>
</string-array>
+
+ <!-- This config is holding calling number conversion map - expected to convert to emergency
+ number. Formats for this config as below:
+ <item>[dialstring1],[dialstring2],[dialstring3]:[replacement]</item>
+
+ E.g. for Taiwan Type Approval, 110 and 119 should be converted to 112.
+ <item>110,119:112</item>
+ -->
+ <string-array translatable="false" name="config_convert_to_emergency_number_map" />
+
+ <!-- An array of packages for which notifications cannot be blocked. -->
+ <string-array translatable="false" name="config_nonBlockableNotificationPackages" />
</resources>
diff --git a/core/res/res/values/donottranslate.xml b/core/res/res/values/donottranslate.xml
index a139529..3a1679c 100644
--- a/core/res/res/values/donottranslate.xml
+++ b/core/res/res/values/donottranslate.xml
@@ -26,4 +26,7 @@
<string name="icu_abbrev_wday_month_day_no_year">eeeMMMMd</string>
<!-- @hide DO NOT TRANSLATE. date formatting pattern for system ui.-->
<string name="system_ui_date_pattern">@string/icu_abbrev_wday_month_day_no_year</string>
+ <!-- @hide DO NOT TRANSLATE Spans within this text are applied to style composing regions
+ within an EditText widget. The text content is ignored and not used. -->
+ <string name="candidates_style" translatable="false"><u>candidates</u></string>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 62b9435..f1118d7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3130,8 +3130,6 @@
<string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
<string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
- <string name="candidates_style"><u>candidates</u></string>
-
<!-- External media notification strings -->
<skip />
@@ -4346,6 +4344,8 @@
<item quantity="other"><xliff:g id="count" example="3">%1$d</xliff:g> selected</item>
</plurals>
+ <string name="default_notification_channel_label">Miscellaneous</string>
+
<string name="importance_from_user">You set the importance of these notifications.</string>
<string name="importance_from_person">This is important because of the people involved.</string>
@@ -4367,6 +4367,9 @@
<string name="language_picker_section_suggested">Suggested</string>
<!-- List section subheader for the language picker, containing a list of all languages available [CHAR LIMIT=30] -->
<string name="language_picker_section_all">All languages</string>
+ <!-- List section subheader for the region picker, containing a list of all regions supported for the selected language.
+ Warning: this is a more 'neutral' term for 'country', not for the sub-divisions of a country. [CHAR LIMIT=30] -->
+ <string name="region_picker_section_all">All regions</string>
<!-- Menu item in the locale menu [CHAR LIMIT=30] -->
<string name="locale_search_menu">Search</string>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 224e3b7..22bdf7e 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -553,11 +553,7 @@
<item name="textOff">@string/capital_off</item>
</style>
- <style name="Widget.Material.BaseButtonBar">
- <item name="background">?attr/colorBackgroundFloating</item>
- </style>
-
- <style name="Widget.Material.ButtonBar" parent="Widget.Material.BaseButtonBar">
+ <style name="Widget.Material.ButtonBar">
<item name="background">@null</item>
</style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 96ab3162..cf6fdc8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1754,6 +1754,7 @@
<java-symbol type="integer" name="config_networkTransitionTimeout" />
<java-symbol type="integer" name="config_networkNotifySwitchType" />
<java-symbol type="array" name="config_networkNotifySwitches" />
+ <java-symbol type="integer" name="config_networkAvoidBadWifi" />
<java-symbol type="integer" name="config_notificationsBatteryFullARGB" />
<java-symbol type="integer" name="config_notificationsBatteryLedOff" />
<java-symbol type="integer" name="config_notificationsBatteryLedOn" />
@@ -2265,8 +2266,8 @@
<java-symbol type="bool" name="config_auto_attach_data_on_creation" />
<java-symbol type="attr" name="closeItemLayout" />
<java-symbol type="layout" name="resolver_different_item_header" />
- <java-symbol type="array" name="config_default_vm_number" />
<java-symbol type="integer" name="config_cdma_3waycall_flash_delay"/>
+ <java-symbol type="array" name="config_default_vm_number" />
<java-symbol type="attr" name="windowBackgroundFallback" />
<java-symbol type="id" name="textSpacerNoButtons" />
<java-symbol type="array" name="dial_string_replace" />
@@ -2469,6 +2470,7 @@
<java-symbol type="dimen" name="notification_content_margin_top" />
<java-symbol type="dimen" name="notification_content_margin_bottom" />
<java-symbol type="dimen" name="notification_header_background_height" />
+ <java-symbol type="string" name="default_notification_channel_label" />
<java-symbol type="string" name="importance_from_user" />
<java-symbol type="string" name="importance_from_person" />
@@ -2516,6 +2518,7 @@
<java-symbol type="menu" name="language_selection_list" />
<java-symbol type="string" name="country_selection_title" />
<java-symbol type="string" name="language_picker_section_all" />
+ <java-symbol type="string" name="region_picker_section_all" />
<java-symbol type="string" name="language_picker_section_suggested" />
<java-symbol type="string" name="language_selection_title" />
<java-symbol type="string" name="search_language_hint" />
@@ -2713,4 +2716,7 @@
<java-symbol type="drawable" name="ic_restart" />
+ <java-symbol type="array" name="config_convert_to_emergency_number_map" />
+
+ <java-symbol type="array" name="config_nonBlockableNotificationPackages" />
</resources>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 2452cfd..cef4784 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -479,6 +479,13 @@
</intent-filter>
</activity>
+ <activity android:name="android.view.ScaleGesture" android:label="ScaleGesture">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name="android.view.StubbedView" android:label="ViewStub">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/core/tests/coretests/res/layout/scale_gesture.xml b/core/tests/coretests/res/layout/scale_gesture.xml
new file mode 100644
index 0000000..05d408d
--- /dev/null
+++ b/core/tests/coretests/res/layout/scale_gesture.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Demonstrates use of scale gesture detector to perform pinch to zoom.
+ See corresponding Java code. -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Hello World!"
+ android:id="@+id/article"
+ android:textSize="12sp"/>
+
+</RelativeLayout>
diff --git a/core/tests/coretests/src/android/os/OsTests.java b/core/tests/coretests/src/android/os/OsTests.java
index 582bf1a..985fa4f 100644
--- a/core/tests/coretests/src/android/os/OsTests.java
+++ b/core/tests/coretests/src/android/os/OsTests.java
@@ -32,6 +32,7 @@
suite.addTestSuite(IdleHandlerTest.class);
suite.addTestSuite(MessageQueueTest.class);
suite.addTestSuite(MessengerTest.class);
+ suite.addTestSuite(PatternMatcherTest.class);
suite.addTestSuite(SystemPropertiesTest.class);
return suite;
diff --git a/core/tests/coretests/src/android/os/PatternMatcherTest.java b/core/tests/coretests/src/android/os/PatternMatcherTest.java
new file mode 100644
index 0000000..9645ccc
--- /dev/null
+++ b/core/tests/coretests/src/android/os/PatternMatcherTest.java
@@ -0,0 +1,234 @@
+package android.os;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class PatternMatcherTest extends TestCase{
+
+ @Test
+ public void testAdvancedPatternMatchesAnyToken() {
+ PatternMatcher matcher = new PatternMatcher(".", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertMatches("a", matcher);
+ assertMatches("b", matcher);
+ assertNotMatches("", matcher);
+ }
+
+ @Test
+ public void testAdvancedPatternMatchesSetToken() {
+ PatternMatcher matcher = new PatternMatcher("[a]", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertMatches("a", matcher);
+ assertNotMatches("b", matcher);
+
+ matcher = new PatternMatcher("[.*+{}\\]\\\\[]", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertMatches(".", matcher);
+ assertMatches("*", matcher);
+ assertMatches("+", matcher);
+ assertMatches("{", matcher);
+ assertMatches("}", matcher);
+ assertMatches("]", matcher);
+ assertMatches("\\", matcher);
+ assertMatches("[", matcher);
+ }
+
+ @Test
+ public void testAdvancedPatternMatchesSetCharacterClassToken() {
+ PatternMatcher matcher = new PatternMatcher("[a-z]", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertMatches("a", matcher);
+ assertMatches("b", matcher);
+ assertNotMatches("A", matcher);
+ assertNotMatches("1", matcher);
+
+ matcher = new PatternMatcher("[a-z][0-9]", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertMatches("a1", matcher);
+ assertNotMatches("1a", matcher);
+ assertNotMatches("aa", matcher);
+
+ matcher = new PatternMatcher("[z-a]", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertNotMatches("a", matcher);
+ assertNotMatches("z", matcher);
+ assertNotMatches("A", matcher);
+
+ matcher = new PatternMatcher("[^0-9]", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertMatches("a", matcher);
+ assertMatches("z", matcher);
+ assertMatches("A", matcher);
+ assertNotMatches("9", matcher);
+ assertNotMatches("5", matcher);
+ assertNotMatches("0", matcher);
+
+ assertPoorlyFormattedPattern("[]a]");
+ matcher = new PatternMatcher("[\\[a]", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertMatches("a", matcher);
+ assertMatches("[", matcher);
+ }
+
+ @Test
+ public void testAdvancedPatternMatchesEscapedCharacters() {
+ PatternMatcher matcher = new PatternMatcher("\\.", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertMatches(".", matcher);
+ assertNotMatches("a", matcher);
+ assertNotMatches("1", matcher);
+
+ matcher = new PatternMatcher("a\\+", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertMatches("a+", matcher);
+ assertNotMatches("a", matcher);
+ assertNotMatches("aaaaa", matcher);
+
+ matcher = new PatternMatcher("[\\a-\\z]", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertMatches("a", matcher);
+ assertMatches("z", matcher);
+ assertNotMatches("A", matcher);
+ }
+
+ @Test
+ public void testAdvancedPatternMatchesLiteralTokens() {
+ PatternMatcher matcher = new PatternMatcher("a", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertNotMatches("", matcher);
+ assertMatches("a", matcher);
+ assertNotMatches("z", matcher);
+
+ matcher = new PatternMatcher("az", PatternMatcher.PATTERN_ADVANCED_GLOB);
+ assertNotMatches("", matcher);
+ assertMatches("az", matcher);
+ assertNotMatches("za", matcher);
+ }
+
+ @Test
+ public void testAdvancedPatternMatchesSetZeroOrMore() {
+ PatternMatcher matcher = new PatternMatcher("[a-z]*", PatternMatcher.PATTERN_ADVANCED_GLOB);
+
+ assertMatches("", matcher);
+ assertMatches("a", matcher);
+ assertMatches("abcdefg", matcher);
+ assertNotMatches("abc1", matcher);
+ assertNotMatches("1abc", matcher);
+ }
+
+ @Test
+ public void testAdvancedPatternMatchesSetOneOrMore() {
+ PatternMatcher matcher = new PatternMatcher("[a-z]+", PatternMatcher.PATTERN_ADVANCED_GLOB);
+
+ assertNotMatches("", matcher);
+ assertMatches("a", matcher);
+ assertMatches("abcdefg", matcher);
+ assertNotMatches("abc1", matcher);
+ assertNotMatches("1abc", matcher);
+ }
+
+
+ @Test
+ public void testAdvancedPatternMatchesSingleRange() {
+ PatternMatcher matcher = new PatternMatcher("[a-z]{1}",
+ PatternMatcher.PATTERN_ADVANCED_GLOB);
+
+ assertNotMatches("", matcher);
+ assertMatches("a", matcher);
+ assertMatches("z", matcher);
+ assertNotMatches("1", matcher);
+ assertNotMatches("aa", matcher);
+ }
+
+ @Test
+ public void testAdvancedPatternMatchesFullRange() {
+ PatternMatcher matcher = new PatternMatcher("[a-z]{1,5}",
+ PatternMatcher.PATTERN_ADVANCED_GLOB);
+
+ assertNotMatches("", matcher);
+ assertMatches("a", matcher);
+ assertMatches("zazaz", matcher);
+ assertNotMatches("azazaz", matcher);
+ assertNotMatches("11111", matcher);
+ }
+
+ @Test
+ public void testAdvancedPatternMatchesPartialRange() {
+ PatternMatcher matcher = new PatternMatcher("[a-z]{3,}",
+ PatternMatcher.PATTERN_ADVANCED_GLOB);
+
+ assertNotMatches("", matcher);
+ assertMatches("aza", matcher);
+ assertMatches("zazaz", matcher);
+ assertMatches("azazazazazaz", matcher);
+ assertNotMatches("aa", matcher);
+ }
+
+ @Test
+ public void testAdvancedPatternMatchesComplexPatterns() {
+ PatternMatcher matcher = new PatternMatcher(
+ "/[0-9]{4}/[0-9]{2}/[0-9]{2}/[a-zA-Z0-9_]+\\.html",
+ PatternMatcher.PATTERN_ADVANCED_GLOB);
+
+ assertNotMatches("", matcher);
+ assertMatches("/2016/09/07/got_this_working.html", matcher);
+ assertMatches("/2016/09/07/got_this_working2.html", matcher);
+ assertNotMatches("/2016/09/07/got_this_working2dothtml", matcher);
+ assertNotMatches("/2016/9/7/got_this_working.html", matcher);
+
+ matcher = new PatternMatcher(
+ "/b*a*bar.*",
+ PatternMatcher.PATTERN_ADVANCED_GLOB);
+
+ assertMatches("/babar", matcher);
+ assertMatches("/babarfff", matcher);
+ assertMatches("/bbaabarfff", matcher);
+ assertMatches("/babar?blah", matcher);
+ assertMatches("/baaaabar?blah", matcher);
+ assertNotMatches("?bar", matcher);
+ assertNotMatches("/bar", matcher);
+ assertNotMatches("/baz", matcher);
+ assertNotMatches("/ba/bar", matcher);
+ assertNotMatches("/barf", matcher);
+ assertNotMatches("/", matcher);
+ assertNotMatches("?blah", matcher);
+ }
+
+ @Test
+ public void testAdvancedPatternPoorFormatThrowsIllegalArgumentException() {
+ assertPoorlyFormattedPattern("[a-z");
+ assertPoorlyFormattedPattern("a{,4}");
+ assertPoorlyFormattedPattern("a{0,a}");
+ assertPoorlyFormattedPattern("a{\\1, 2}");
+ assertPoorlyFormattedPattern("[]");
+ assertPoorlyFormattedPattern("a{}");
+ assertPoorlyFormattedPattern("{3,4}");
+ assertPoorlyFormattedPattern("a+{3,4}");
+ assertPoorlyFormattedPattern("*.");
+ assertPoorlyFormattedPattern(".+*");
+ assertPoorlyFormattedPattern("a{3,4");
+ assertPoorlyFormattedPattern("[a");
+ assertPoorlyFormattedPattern("abc\\");
+ assertPoorlyFormattedPattern("+.");
+
+ StringBuilder charSet = new StringBuilder("[");
+ for (int i = 0; i < 1024; i++) {
+ charSet.append('a' + (i % 26));
+ }
+ charSet.append("]");
+ assertPoorlyFormattedPattern(charSet.toString());
+ }
+
+ private void assertMatches(String string, PatternMatcher matcher) {
+ assertTrue("'" + string + "' should match '" + matcher.toString() + "'",
+ matcher.match(string));
+ }
+
+ private void assertNotMatches(String string, PatternMatcher matcher) {
+ assertTrue("'" + string + "' should not match '" + matcher.toString() + "'",
+ !matcher.match(string));
+ }
+
+ private void assertPoorlyFormattedPattern(String format) {
+ try {
+ new PatternMatcher(format, PatternMatcher.PATTERN_ADVANCED_GLOB);
+ } catch (IllegalArgumentException e) {
+ return;// expected
+ }
+
+ fail("'" + format + "' was erroneously created");
+ }
+}
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutTest.java b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
new file mode 100644
index 0000000..9362ed9
--- /dev/null
+++ b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.text;
+
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+import android.text.style.ReplacementSpan;
+import junit.framework.TestCase;
+
+public class DynamicLayoutTest extends TestCase {
+ private static final int WIDTH = 10000;
+
+ public void testGetBlocksAlwaysNeedToBeRedrawn_en() {
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+ final DynamicLayout layout = new DynamicLayout(builder, new TextPaint(), WIDTH,
+ ALIGN_NORMAL, 0, 0, false);
+
+ assertNull(layout.getBlocksAlwaysNeedToBeRedrawn());
+
+ builder.append("abcd efg\n");
+ builder.append("hijk lmn\n");
+ assertNull(layout.getBlocksAlwaysNeedToBeRedrawn());
+
+ builder.delete(0, builder.length());
+ assertNull(layout.getBlocksAlwaysNeedToBeRedrawn());
+ }
+
+
+ private static class MockReplacementSpan extends ReplacementSpan {
+ @Override
+ public int getSize(Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
+ return 10;
+ }
+
+ @Override
+ public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top,
+ int y, int bottom, Paint paint) {
+ }
+ }
+
+ public void testGetBlocksAlwaysNeedToBeRedrawn_replacementSpan() {
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+ final DynamicLayout layout = new DynamicLayout(builder, new TextPaint(), WIDTH,
+ ALIGN_NORMAL, 0, 0, false);
+
+ assertNull(layout.getBlocksAlwaysNeedToBeRedrawn());
+
+ builder.append("abcd efg\n");
+ builder.append("hijk lmn\n");
+ assertNull(layout.getBlocksAlwaysNeedToBeRedrawn());
+
+ builder.setSpan(new MockReplacementSpan(), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertNotNull(layout.getBlocksAlwaysNeedToBeRedrawn());
+ assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0));
+
+ builder.setSpan(new MockReplacementSpan(), 9, 13, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0));
+ assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(1));
+
+ builder.delete(9, 13);
+ assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0));
+ assertFalse(layout.getBlocksAlwaysNeedToBeRedrawn().contains(1));
+
+ builder.delete(0, 4);
+ assertFalse(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0));
+ assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().isEmpty());
+ }
+
+ public void testGetBlocksAlwaysNeedToBeRedrawn_thai() {
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+ final DynamicLayout layout = new DynamicLayout(builder, new TextPaint(), WIDTH,
+ ALIGN_NORMAL, 0, 0, false);
+
+ assertNull(layout.getBlocksAlwaysNeedToBeRedrawn());
+
+ builder.append("\u0E22\u0E34\u0E19\u0E14\u0E35\u0E15\u0E49\u0E2D\u0E19\u0E23\u0E31\u0E1A");
+ builder.append("\u0E2A\u0E39\u0E48");
+ assertNull(layout.getBlocksAlwaysNeedToBeRedrawn());
+
+ builder.append("\u0E48\u0E48\u0E48\u0E48\u0E48");
+ assertNotNull(layout.getBlocksAlwaysNeedToBeRedrawn());
+ assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0));
+
+ builder.delete(builder.length() -5, builder.length());
+ assertFalse(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0));
+ assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().isEmpty());
+ }
+}
diff --git a/core/tests/coretests/src/android/view/PinchZoomAction.java b/core/tests/coretests/src/android/view/PinchZoomAction.java
new file mode 100644
index 0000000..78a4b31
--- /dev/null
+++ b/core/tests/coretests/src/android/view/PinchZoomAction.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import static android.support.test.espresso.core.deps.guava.base.Preconditions.checkNotNull;
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
+import static org.hamcrest.Matchers.allOf;
+
+import android.os.SystemClock;
+import android.support.test.espresso.InjectEventSecurityException;
+import android.support.test.espresso.PerformException;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.MotionEvents;
+import android.support.test.espresso.action.Swiper;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.util.HumanReadables;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import javax.annotation.Nullable;
+import org.hamcrest.Matcher;
+
+/**
+ * Pinch and zooms on a View using touch events.
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be displayed on screen
+ * <ul>
+ */
+public class PinchZoomAction implements ViewAction {
+ public static Swiper.Status sendPinchZoomAction(UiController uiController,
+ float[] firstFingerStartCoords,
+ float[] firstFingerEndCoords,
+ float[] secondFingerStartCoords,
+ float[] secondFingerEndCoords,
+ float[] precision) {
+ checkNotNull(uiController);
+ checkNotNull(firstFingerStartCoords);
+ checkNotNull(firstFingerEndCoords);
+ checkNotNull(secondFingerStartCoords);
+ checkNotNull(secondFingerEndCoords);
+ checkNotNull(precision);
+
+ // Specify the touch properties for the finger events.
+ final MotionEvent.PointerProperties pp1 = new MotionEvent.PointerProperties();
+ pp1.id = 0;
+ pp1.toolType = MotionEvent.TOOL_TYPE_FINGER;
+ final MotionEvent.PointerProperties pp2 = new MotionEvent.PointerProperties();
+ pp2.id = 1;
+ pp2.toolType = MotionEvent.TOOL_TYPE_FINGER;
+ MotionEvent.PointerProperties[] pointerProperties =
+ new MotionEvent.PointerProperties[]{pp1, pp2};
+
+ // Specify the motion properties of the two touch points.
+ final MotionEvent.PointerCoords pc1 = new MotionEvent.PointerCoords();
+ pc1.x = firstFingerStartCoords[0];
+ pc1.y = firstFingerStartCoords[1];
+ pc1.pressure = 1;
+ pc1.size = 1;
+ final MotionEvent.PointerCoords pc2 = new MotionEvent.PointerCoords();
+ pc2.x = secondFingerStartCoords[0];
+ pc2.y = secondFingerEndCoords[1];
+ pc2.pressure = 1;
+ pc2.size = 1;
+
+ final long startTime = SystemClock.uptimeMillis();
+ long eventTime = startTime;
+ final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[]{pc1, pc2};
+
+ final MotionEvent firstFingerEvent = MotionEvent.obtain(startTime,
+ eventTime, MotionEvent.ACTION_DOWN, 1, pointerProperties, pointerCoords,
+ 0, 0, 1, 1, 0, 0, 0, 0);
+
+ eventTime = SystemClock.uptimeMillis();
+ final MotionEvent secondFingerEvent = MotionEvent.obtain(startTime, eventTime,
+ MotionEvent.ACTION_POINTER_DOWN +
+ (pp2.id << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+ 2, pointerProperties, pointerCoords, 0, 0, 1, 1, 0, 0, 0, 0);
+
+ try {
+ uiController.injectMotionEvent(firstFingerEvent);
+ } catch (InjectEventSecurityException e) {
+ throw new PerformException.Builder()
+ .withActionDescription("First finger down event")
+ .withViewDescription("Scale gesture detector")
+ .withCause(e)
+ .build();
+ }
+
+ try {
+ uiController.injectMotionEvent(secondFingerEvent);
+ } catch (InjectEventSecurityException e) {
+ throw new PerformException.Builder()
+ .withActionDescription("Second finger down event")
+ .withViewDescription("Scale gesture detector")
+ .withCause(e)
+ .build();
+ }
+
+ // Specify the coordinates of the two touch points.
+ final float[][] stepsFirstFinger = interpolate(firstFingerStartCoords,
+ firstFingerEndCoords);
+ final float[][] stepsSecondFinger = interpolate(secondFingerStartCoords,
+ secondFingerEndCoords);
+
+ // Loop until the end points of the two fingers are reached.
+ for (int i = 0; i < PINCH_STEP_COUNT; i++) {
+ eventTime = SystemClock.uptimeMillis();
+
+ pc1.x = stepsFirstFinger[i][0];
+ pc1.y = stepsFirstFinger[i][1];
+ pc2.x = stepsSecondFinger[i][0];
+ pc2.y = stepsSecondFinger[i][1];
+
+ final MotionEvent event = MotionEvent.obtain(startTime, eventTime,
+ MotionEvent.ACTION_MOVE, 2, pointerProperties, pointerCoords,
+ 0, 0, 1, 1, 0, 0, 0, 0);
+
+ try {
+ uiController.injectMotionEvent(event);
+ } catch (InjectEventSecurityException e) {
+ throw new PerformException.Builder()
+ .withActionDescription("Move event")
+ .withViewDescription("Scale gesture event")
+ .withCause(e)
+ .build();
+ }
+
+ uiController.loopMainThreadForAtLeast(800);
+ }
+
+ eventTime = SystemClock.uptimeMillis();
+
+ // Send the up event for the second finger.
+ final MotionEvent secondFingerUpEvent = MotionEvent.obtain(startTime, eventTime,
+ MotionEvent.ACTION_POINTER_UP, 2, pointerProperties, pointerCoords,
+ 0, 0, 1, 1, 0, 0, 0, 0);
+ try {
+ uiController.injectMotionEvent(secondFingerUpEvent);
+ } catch (InjectEventSecurityException e) {
+ throw new PerformException.Builder()
+ .withActionDescription("Second finger up event")
+ .withViewDescription("Scale gesture detector")
+ .withCause(e)
+ .build();
+ }
+
+ eventTime = SystemClock.uptimeMillis();
+ // Send the up event for the first finger.
+ final MotionEvent firstFingerUpEvent = MotionEvent.obtain(startTime, eventTime,
+ MotionEvent.ACTION_POINTER_UP, 1, pointerProperties, pointerCoords,
+ 0, 0, 1, 1, 0, 0, 0, 0);
+ try {
+ uiController.injectMotionEvent(firstFingerUpEvent);
+ } catch (InjectEventSecurityException e) {
+ throw new PerformException.Builder()
+ .withActionDescription("First finger up event")
+ .withViewDescription("Scale gesture detector")
+ .withCause(e)
+ .build();
+ }
+ return Swiper.Status.SUCCESS;
+ }
+
+ private static float[][] interpolate(float[] start, float[] end) {
+ float[][] res = new float[PINCH_STEP_COUNT][2];
+
+ for (int i = 0; i < PINCH_STEP_COUNT; i++) {
+ res[i][0] = start[0] + (end[0] - start[0]) * i / (PINCH_STEP_COUNT - 1f);
+ res[i][1] = start[1] + (end[1] - start[1]) * i / (PINCH_STEP_COUNT - 1f);
+ }
+
+ return res;
+ }
+
+ /** The number of move events to send for each pinch. */
+ private static final int PINCH_STEP_COUNT = 10;
+
+ private final Class<? extends View> mViewClass;
+ private final float[] mFirstFingerStartCoords;
+ private final float[] mFirstFingerEndCoords;
+ private final float[] mSecondFingerStartCoords;
+ private final float[] mSecondFingerEndCoords;
+
+ public PinchZoomAction(float[] firstFingerStartCoords,
+ float[] firstFingerEndCoords,
+ float[] secondFingerStartCoords,
+ float[] secondFingerEndCoords,
+ Class<? extends View> viewClass) {
+ mFirstFingerStartCoords = firstFingerStartCoords;
+ mFirstFingerEndCoords = firstFingerEndCoords;
+ mSecondFingerStartCoords = secondFingerStartCoords;
+ mSecondFingerEndCoords = secondFingerEndCoords;
+ mViewClass = viewClass;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Matcher<View> getConstraints() {
+ return allOf(isCompletelyDisplayed(), isAssignableFrom(mViewClass));
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ checkNotNull(uiController);
+ checkNotNull(view);
+ Swiper.Status status;
+ final float[] precision = {1.0f, 1.0f, 1.0f, 1.0f};
+
+ try {
+ status = sendPinchZoomAction(uiController, this.mFirstFingerStartCoords,
+ this.mFirstFingerEndCoords, this.mSecondFingerStartCoords,
+ this.mSecondFingerEndCoords, precision);
+ } catch (RuntimeException re) {
+ throw new PerformException.Builder()
+ .withActionDescription(getDescription())
+ .withViewDescription(HumanReadables.describe(view))
+ .withCause(re)
+ .build();
+ }
+ if (status == Swiper.Status.FAILURE) {
+ throw new PerformException.Builder()
+ .withActionDescription(getDescription())
+ .withViewDescription(HumanReadables.describe(view))
+ .withCause(new RuntimeException(getDescription() + " failed"))
+ .build();
+ }
+ }
+
+ @Override
+ public String getDescription() {
+ return "Pinch Zoom Action";
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ScaleGesture.java b/core/tests/coretests/src/android/view/ScaleGesture.java
new file mode 100644
index 0000000..a954a4a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ScaleGesture.java
@@ -0,0 +1,58 @@
+/*
+* Copyright (C) 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener;
+import android.widget.TextView;
+
+public class ScaleGesture extends Activity {
+ private ScaleGestureDetector mScaleGestureDetector;
+ private float mFactor;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.scale_gesture);
+ mScaleGestureDetector = new ScaleGestureDetector(this, new OnScaleGestureListener());
+ mFactor = 1.0f;
+ }
+
+ public float getScaleFactor() {
+ return mFactor;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ mScaleGestureDetector.onTouchEvent(event);
+ return true;
+ }
+
+ public class OnScaleGestureListener extends SimpleOnScaleGestureListener {
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ mFactor *= detector.getScaleFactor();
+ return true;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java b/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
new file mode 100644
index 0000000..23d0251
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
@@ -0,0 +1,91 @@
+/*
+* Copyright (C) 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package android.view;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.DisplayMetrics;
+import android.view.PinchZoomAction;
+import android.view.ScaleGesture;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.Espresso.onView;
+
+public class ScaleGestureDetectorTest extends ActivityInstrumentationTestCase2<ScaleGesture> {
+ private ScaleGesture mScaleGestureActivity;
+
+ public ScaleGestureDetectorTest() {
+ super("com.android.frameworks.coretests", ScaleGesture.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+ mScaleGestureActivity = getActivity();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testScaleGestureDetector() {
+ // No scaling should have occurred prior to performing pinch zoom action.
+ final float initialScaleFactor = 1.0f;
+ assertEquals(initialScaleFactor, mScaleGestureActivity.getScaleFactor());
+
+ // Specify start and end coordinates, irrespective of device display size.
+ final DisplayMetrics dm = new DisplayMetrics();
+ final WindowManager wm = (WindowManager) (mScaleGestureActivity.getApplicationContext())
+ .getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getMetrics(dm);
+ final int displayWidth = dm.widthPixels;
+ final int displayHeight = dm.heightPixels;
+
+ // Obtain coordinates to perform pinch and zoom from the center, to 75% of the display.
+ final int centerX = displayWidth / 2;
+ final int centerY = displayHeight / 2;
+
+ // Offset center coordinates by one, so that the two starting points are different.
+ final float[] firstFingerStartCoords = new float[] {centerX + 1.0f, centerY - 1.0f};
+ final float[] firstFingerEndCoords =
+ new float[] {0.75f * displayWidth, 0.25f * displayHeight};
+ final float[] secondFingerStartCoords = new float[] {centerX - 1.0f, centerY + 1.0f};
+ final float[] secondFingerEndCoords =
+ new float[] {0.25f * displayWidth, 0.75f * displayHeight};
+
+ onView(withId(R.id.article)).perform(new PinchZoomAction(firstFingerStartCoords,
+ firstFingerEndCoords, secondFingerStartCoords, secondFingerEndCoords,
+ TextView.class));
+
+ // Text should have been 'zoomed', meaning scale factor increased.
+ assertTrue(mScaleGestureActivity.getScaleFactor() > initialScaleFactor);
+ }
+}
\ No newline at end of file
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 117e17b..345ec37 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -350,7 +350,7 @@
<family lang="und-Zsye">
<font weight="400" style="normal">NotoColorEmoji.ttf</font>
</family>
- <family>
+ <family lang="und-Zsym">
<font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
</family>
<family>
diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml
index d75562d..7e7c2de 100644
--- a/docs/html/_redirects.yaml
+++ b/docs/html/_redirects.yaml
@@ -1,6 +1,8 @@
# For information about this file's format, see
# https://developers.google.com/internal/publishing/redirects
redirects:
+- from: /guide/topics/fundamentals/fragments.html
+ to: /guide/components/fragments.html
- from: /about/versions/index.html
to: /about/index.html
- from: /about/versions/api-levels.html
diff --git a/docs/html/about/dashboards/index.jd b/docs/html/about/dashboards/index.jd
index f5d23e8..2721c85 100644
--- a/docs/html/about/dashboards/index.jd
+++ b/docs/html/about/dashboards/index.jd
@@ -59,7 +59,7 @@
</div>
-<p style="clear:both"><em>Data collected during a 7-day period ending on August 1, 2016.
+<p style="clear:both"><em>Data collected during a 7-day period ending on September 5, 2016.
<br/>Any versions with less than 0.1% distribution are not shown.</em>
</p>
@@ -81,7 +81,7 @@
</div>
-<p style="clear:both"><em>Data collected during a 7-day period ending on August 1, 2016.
+<p style="clear:both"><em>Data collected during a 7-day period ending on September 5, 2016.
<br/>Any screen configurations with less than 0.1% distribution are not shown.</em></p>
@@ -101,7 +101,7 @@
<img alt="" style="float:right"
-src="//chart.googleapis.com/chart?chl=GL%202.0%7CGL%203.0%7CGL%203.1&chf=bg%2Cs%2C00000000&chd=t%3A46.0%2C42.6%2C11.4&chco=c4df9b%2C6fad0c&cht=p&chs=400x250">
+src="//chart.googleapis.com/chart?chl=GL%202.0%7CGL%203.0%7CGL%203.1&chf=bg%2Cs%2C00000000&chd=t%3A44.9%2C42.3%2C12.8&chco=c4df9b%2C6fad0c&cht=p&chs=400x250">
<p>To declare which version of OpenGL ES your application requires, you should use the {@code
android:glEsVersion} attribute of the <a
@@ -119,21 +119,21 @@
</tr>
<tr>
<td>2.0</td>
-<td>46.0%</td>
+<td>44.9%</td>
</tr>
<tr>
<td>3.0</td>
-<td>42.6%</td>
+<td>42.3%</td>
</tr>
<tr>
<td>3.1</td>
-<td>11.4%</td>
+<td>12.8%</td>
</tr>
</table>
-<p style="clear:both"><em>Data collected during a 7-day period ending on August 1, 2016</em></p>
+<p style="clear:both"><em>Data collected during a 7-day period ending on September 5, 2016</em></p>
@@ -147,19 +147,19 @@
"Large": {
"hdpi": "0.5",
"ldpi": "0.2",
- "mdpi": "4.3",
+ "mdpi": "4.1",
"tvdpi": "2.1",
"xhdpi": "0.5"
},
"Normal": {
- "hdpi": "40.0",
- "mdpi": "3.8",
- "tvdpi": "0.1",
- "xhdpi": "27.3",
+ "hdpi": "39.5",
+ "mdpi": "3.5",
+ "tvdpi": "0.2",
+ "xhdpi": "28.4",
"xxhdpi": "15.5"
},
"Small": {
- "ldpi": "1.8"
+ "ldpi": "1.6"
},
"Xlarge": {
"hdpi": "0.3",
@@ -167,8 +167,8 @@
"xhdpi": "0.7"
}
},
- "densitychart": "//chart.googleapis.com/chart?chd=t%3A2.0%2C11.0%2C2.2%2C40.8%2C28.5%2C15.5&chf=bg%2Cs%2C00000000&chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&cht=p&chs=400x250&chco=c4df9b%2C6fad0c",
- "layoutchart": "//chart.googleapis.com/chart?chd=t%3A3.9%2C7.6%2C86.7%2C1.8&chf=bg%2Cs%2C00000000&chl=Xlarge%7CLarge%7CNormal%7CSmall&cht=p&chs=400x250&chco=c4df9b%2C6fad0c"
+ "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chd=t%3A1.8%2C10.5%2C2.3%2C40.4%2C29.6%2C15.5&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&cht=p&chs=400x250",
+ "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chd=t%3A3.9%2C7.4%2C87.2%2C1.6&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&cht=p&chs=400x250"
}
];
@@ -176,7 +176,7 @@
var VERSION_DATA =
[
{
- "chart": "//chart.googleapis.com/chart?chd=t%3A0.1%2C1.7%2C1.6%2C16.7%2C29.2%2C35.5%2C15.2&chf=bg%2Cs%2C00000000&chl=Froyo%7CGingerbread%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat%7CLollipop%7CMarshmallow&cht=p&chs=500x250&chco=c4df9b%2C6fad0c",
+ "chart": "//chart.googleapis.com/chart?chl=Froyo%7CGingerbread%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat%7CLollipop%7CMarshmallow&chd=t%3A0.1%2C1.5%2C1.4%2C15.6%2C27.7%2C35.0%2C18.7&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&cht=p&chs=500x250",
"data": [
{
"api": 8,
@@ -186,47 +186,47 @@
{
"api": 10,
"name": "Gingerbread",
- "perc": "1.7"
+ "perc": "1.5"
},
{
"api": 15,
"name": "Ice Cream Sandwich",
- "perc": "1.6"
+ "perc": "1.4"
},
{
"api": 16,
"name": "Jelly Bean",
- "perc": "6.0"
+ "perc": "5.6"
},
{
"api": 17,
"name": "Jelly Bean",
- "perc": "8.3"
+ "perc": "7.7"
},
{
"api": 18,
"name": "Jelly Bean",
- "perc": "2.4"
+ "perc": "2.3"
},
{
"api": 19,
"name": "KitKat",
- "perc": "29.2"
+ "perc": "27.7"
},
{
"api": 21,
"name": "Lollipop",
- "perc": "14.1"
+ "perc": "13.1"
},
{
"api": 22,
"name": "Lollipop",
- "perc": "21.4"
+ "perc": "21.9"
},
{
"api": 23,
"name": "Marshmallow",
- "perc": "15.2"
+ "perc": "18.7"
}
]
}
diff --git a/docs/html/guide/topics/location/strategies.jd b/docs/html/guide/topics/location/strategies.jd
index 2dfed2c..548ed9c 100755
--- a/docs/html/guide/topics/location/strategies.jd
+++ b/docs/html/guide/topics/location/strategies.jd
@@ -133,36 +133,69 @@
both to zero requests location notifications as frequently as possible. The last parameter is your
{@link android.location.LocationListener}, which receives callbacks for location updates.</p>
-<p>To request location updates from the GPS provider,
-substitute <code>GPS_PROVIDER</code> for <code>NETWORK_PROVIDER</code>. You can also request
-location updates from both the GPS and the Network Location Provider by calling {@link
-android.location.LocationManager#requestLocationUpdates requestLocationUpdates()} twice—once
-for <code>NETWORK_PROVIDER</code> and once for <code>GPS_PROVIDER</code>.</p>
+<p>To request location updates from the GPS provider, use {@link
+android.location.LocationManager#GPS_PROVIDER} instead of {@link
+android.location.LocationManager#NETWORK_PROVIDER}. You can also request
+location updates from both the GPS and the Network Location Provider by calling
+{@link android.location.LocationManager#requestLocationUpdates
+requestLocationUpdates()} twice—once for {@link
+android.location.LocationManager#NETWORK_PROVIDER} and once for {@link
+android.location.LocationManager#GPS_PROVIDER}.</p>
<h3 id="Permission">Requesting User Permissions</h3>
-<p>In order to receive location updates from <code>NETWORK_PROVIDER</code> or
-<code>GPS_PROVIDER</code>, you must request user permission by declaring either the {@code
-ACCESS_COARSE_LOCATION} or {@code ACCESS_FINE_LOCATION} permission, respectively, in your Android
-manifest file. For example:</p>
+<p>
+ In order to receive location updates from {@link
+ android.location.LocationManager#NETWORK_PROVIDER} or {@link
+ android.location.LocationManager#GPS_PROVIDER}, you must request the user's
+ permission by declaring either the {@code ACCESS_COARSE_LOCATION} or {@code
+ ACCESS_FINE_LOCATION} permission, respectively, in your Android manifest file.
+ Without these permissions, your application will fail at runtime when
+ requesting location updates.
+</p>
+<p>
+ If you are using both {@link
+ android.location.LocationManager#NETWORK_PROVIDER} and {@link
+ android.location.LocationManager#GPS_PROVIDER}, then you need to request only
+ the {@code ACCESS_FINE_LOCATION} permission, because it includes permission
+ for both providers. Permission for {@code ACCESS_COARSE_LOCATION} allows
+ access only to {@link android.location.LocationManager#NETWORK_PROVIDER}.
+</p>
+
+<p id="location-feature-caution" class="caution">
+ <strong>Caution:</strong> If your app targets Android 5.0 (API level 21) or
+ higher, you <em>must</em> declare that your app uses the
+ <code>android.hardware.location.network</code> or
+ <code>android.hardware.location.gps</code> hardware feature in the manifest
+ file, depending on whether your app receives location updates from {@link
+ android.location.LocationManager#NETWORK_PROVIDER} or from {@link
+ android.location.LocationManager#GPS_PROVIDER}. If your app receives location
+ information from either of these location provider sources, you need to
+ declare that the app uses these hardware features in your app manifest.
+ On devices running verions prior to Android 5.0 (API 21), requesting the
+ {@code ACCESS_FINE_LOCATION} or {@code ACCESS_COARSE_LOCATION} permission
+ includes an implied request for location hardware features. However,
+ requesting those permissions <em>does not</em> automatically request location
+ hardware features on Android 5.0 (API level 21) and higher.
+</p>
+
+<p>
+ The following code sample demonstrates how to declare the permission and
+ hardware feature in the manifest file of an app that reads data from the
+ device's GPS:
+</p>
<pre>
<manifest ... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
+ <!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
+ <uses-feature android:name="android.hardware.location.gps" />
+ ...
</manifest>
</pre>
-<p>Without these permissions, your application will fail at runtime when requesting
-location updates.</p>
-
-<p class="note"><strong>Note:</strong> If you are using both <code>NETWORK_PROVIDER</code> and
-<code>GPS_PROVIDER</code>, then you need to request only the {@code ACCESS_FINE_LOCATION}
-permission, because it includes permission for both providers. (Permission for {@code
-ACCESS_COARSE_LOCATION} includes permission only for <code>NETWORK_PROVIDER</code>.)</p>
-
-
<h2 id="BestPerformance">Defining a Model for the Best Performance</h2>
<p>Location-based applications are now commonplace, but due to the less than optimal
@@ -404,9 +437,10 @@
the Android emulator. There are three different ways to send your application mock location
data: using Android Studio, DDMS, or the "geo" command in the emulator console.</p>
-<p class="note"><strong>Note:</strong> Providing mock location data is injected as GPS location
-data, so you must request location updates from <code>GPS_PROVIDER</code> in order for mock location
-data to work.</p>
+<p class="note"><strong>Note:</strong> Providing mock location data is injected
+as GPS location data, so you must request location updates from {@link
+android.location.LocationManager#GPS_PROVIDER} in order for mock location data
+to work.</p>
<h3 id="MockAVD">Using Android Studio</h3>
diff --git a/docs/html/guide/topics/manifest/uses-feature-element.jd b/docs/html/guide/topics/manifest/uses-feature-element.jd
index 9b32244..26ae59f 100755
--- a/docs/html/guide/topics/manifest/uses-feature-element.jd
+++ b/docs/html/guide/topics/manifest/uses-feature-element.jd
@@ -1666,6 +1666,15 @@
<pre><uses-feature android:name="android.hardware.camera" android:required="false" /></pre>
+<p class="caution">
+ <strong>Caution:</strong> If your app targets Android 5.0 (API level 21) or
+ higher and uses the <code>ACCESS_COARSE_LOCATION</code> or
+ <code>ACCESS_FINE_LOCATION</code> permission in order to receive location
+ updates from the network or a GPS, respectively, you must also explicitly
+ declare that your app uses the <code>android.hardware.location.network</code>
+ or <code>android.hardware.location.gps</code> hardware features.
+</p>
+
<p class="table-caption" id="permissions-features">
<strong>Table 2. </strong>Device permissions that imply device hardware use.
</p>
@@ -1717,14 +1726,29 @@
</tr>
<tr>
<td><code>ACCESS_COARSE_LOCATION</code></td>
- <td><code>android.hardware.location.network</code> <em>and</em>
-<br><code>android.hardware.location</code></td>
+ <td>
+ <p>
+ <code>android.hardware.location</code>
+ </p>
+ <p>
+ <code>android.hardware.location.network</code>
+ (Only when target API level is 20 orlower.)
+ </p>
+ </td>
<!-- <td></td> -->
</tr>
<tr>
<td><code>ACCESS_FINE_LOCATION</code></td>
- <td><code>android.hardware.location.gps</code> <em>and</em>
-<br><code>android.hardware.location</code></td>
+ <td>
+ <p>
+ <code>android.hardware.location</code>
+ </p>
+ <p>
+ <code>android.hardware.location.gps</code>
+ (Only when target API level is 20 orlower.)
+ </p>
+ </td>
+
<!-- <td></td> -->
</tr>
diff --git a/docs/html/guide/topics/media/camera.jd b/docs/html/guide/topics/media/camera.jd
index 4995a13d..383b6c1 100644
--- a/docs/html/guide/topics/media/camera.jd
+++ b/docs/html/guide/topics/media/camera.jd
@@ -154,10 +154,16 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
</pre>
</li>
- <li><strong>Location Permission</strong> - If your application tags images with GPS location
-information, you must request location permission:
+ <li>
+ <p><strong>Location Permission</strong> - If your application tags images
+ with GPS location information, you must request the {@code ACCESS_FINE_LOCATION}
+ permission. Note that, if your app targets Android 5.0 (API level 21) or
+ higher, you also need to declare that your app uses the device's GPS:</p>
<pre>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+...
+<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
+<uses-feature android:name="android.hardware.location.gps" />
</pre>
<p>For more information about getting user location, see
<a href="{@docRoot}guide/topics/location/strategies.html">Location Strategies</a>.</p>
diff --git a/docs/html/images/training/ctl-config.png b/docs/html/images/training/ctl-config.png
index 82f63c8..3a4f738 100644
--- a/docs/html/images/training/ctl-config.png
+++ b/docs/html/images/training/ctl-config.png
Binary files differ
diff --git a/docs/html/images/training/tv/playback/onboarding-fragment-diagram.png b/docs/html/images/training/tv/playback/onboarding-fragment-diagram.png
new file mode 100644
index 0000000..5839a50
--- /dev/null
+++ b/docs/html/images/training/tv/playback/onboarding-fragment-diagram.png
Binary files differ
diff --git a/docs/html/images/training/tv/playback/onboarding-fragment.png b/docs/html/images/training/tv/playback/onboarding-fragment.png
new file mode 100644
index 0000000..5b7da55
--- /dev/null
+++ b/docs/html/images/training/tv/playback/onboarding-fragment.png
Binary files differ
diff --git a/docs/html/images/training/tv/playback/onboarding-fragment_2x.png b/docs/html/images/training/tv/playback/onboarding-fragment_2x.png
new file mode 100644
index 0000000..0034be4
--- /dev/null
+++ b/docs/html/images/training/tv/playback/onboarding-fragment_2x.png
Binary files differ
diff --git a/docs/html/jd_extras_en.js b/docs/html/jd_extras_en.js
index dfc30c3..f3469b4 100644
--- a/docs/html/jd_extras_en.js
+++ b/docs/html/jd_extras_en.js
@@ -156,6 +156,16 @@
"lang":"en"
},
{
+ "title":"GPU Debugger",
+ "summary":"Use the GPU Debugger to analyze and debug your OpenGL ES apps. Inspect the GPU state and understand what caused a specific rendering outcome.",
+ "url":"studio/debug/am-gpu-debugger.html",
+ "image":"images/tools/thumbnails/am-gpu-debugger_2-2_2x.png",
+ "type":"tools",
+ "keywords": ["android","performance","profiling","tools","monitor","debug"],
+ "tags": ["android","performance","profiling","tools","monitor","debug"],
+ "lang":"en"
+ },
+ {
"title":"HPROF Viewer and Analyzer",
"summary":"Use the Memory Monitor to dump the Java heap to an HPROF file. The HPROF Viewer displays classes, instances of each class, and a reference tree to help you track memory usage and find memory leaks.",
"url":"studio/profile/am-hprof.html",
@@ -5453,6 +5463,12 @@
"studio/profile/am-sysinfo.html"
]
},
+"tools/help/gpu": {
+ "title": "",
+ "resources": [
+ "studio/debug/am-gpu-debugger.html"
+ ]
+ },
"tools/help/shot": {
"title": "",
"resources": [
diff --git a/docs/html/topic/libraries/data-binding/index.jd b/docs/html/topic/libraries/data-binding/index.jd
index ddcc9f2..0faa1db 100644
--- a/docs/html/topic/libraries/data-binding/index.jd
+++ b/docs/html/topic/libraries/data-binding/index.jd
@@ -162,7 +162,9 @@
<p>
To use data binding, Android Plugin for Gradle <strong>1.5.0-alpha1</strong>
- or higher is required.
+ or higher is required. See how to <a
+href="/studio/releases/gradle-plugin.html#updating-plugin">update the Android
+Plugin for Gradle</a>.
</p>
<h2 id="build_environment">
diff --git a/docs/html/topic/libraries/support-library/features.jd b/docs/html/topic/libraries/support-library/features.jd
index 614392e..b5f189a 100755
--- a/docs/html/topic/libraries/support-library/features.jd
+++ b/docs/html/topic/libraries/support-library/features.jd
@@ -108,7 +108,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:support-compat:24.2.0
+com.android.support:support-compat:24.2.1
</pre>
<h3 id="v4-core-utils">v4 core-utils library</h3>
@@ -124,7 +124,7 @@
</p>
<pre>
-com.android.support:support-core-utils:24.2.0
+com.android.support:support-core-utils:24.2.1
</pre>
<h3 id="v4-core-ui">v4 core-ui library</h3>
@@ -141,7 +141,7 @@
</p>
<pre>
-com.android.support:support-core-ui:24.2.0
+com.android.support:support-core-ui:24.2.1
</pre>
<h3 id="v4-media-compat">v4 media-compat library</h3>
@@ -158,7 +158,7 @@
</p>
<pre>
-com.android.support:support-media-compat:24.2.0
+com.android.support:support-media-compat:24.2.1
</pre>
<h3 id="v4-fragment">v4 fragment library</h3>
@@ -178,7 +178,7 @@
</p>
<pre>
-com.android.support:support-fragment:24.2.0
+com.android.support:support-fragment:24.2.1
</pre>
<h2 id="multidex">Multidex Support Library</h2>
@@ -245,7 +245,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:appcompat-v7:24.2.0
+com.android.support:appcompat-v7:24.2.1
</pre>
@@ -260,7 +260,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:cardview-v7:24.2.0
+com.android.support:cardview-v7:24.2.1
</pre>
@@ -276,7 +276,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:gridlayout-v7:24.2.0
+com.android.support:gridlayout-v7:24.2.1
</pre>
@@ -299,7 +299,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:mediarouter-v7:24.2.0
+com.android.support:mediarouter-v7:24.2.1
</pre>
<p class="caution">The v7 mediarouter library APIs introduced in Support Library
@@ -319,7 +319,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:palette-v7:24.2.0
+com.android.support:palette-v7:24.2.1
</pre>
@@ -335,7 +335,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:recyclerview-v7:24.2.0
+com.android.support:recyclerview-v7:24.2.1
</pre>
@@ -358,7 +358,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:preference-v7:24.2.0
+com.android.support:preference-v7:24.2.1
</pre>
<h2 id="v8">v8 Support Library</h2>
@@ -409,7 +409,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:support-v13:24.2.0
+com.android.support:support-v13:24.2.1
</pre>
@@ -435,7 +435,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:preference-v14:24.2.0
+com.android.support:preference-v14:24.2.1
</pre>
@@ -458,7 +458,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:preference-leanback-v17:24.2.0
+com.android.support:preference-leanback-v17:24.2.1
</pre>
@@ -494,7 +494,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:leanback-v17:24.2.0
+com.android.support:leanback-v17:24.2.1
</pre>
@@ -509,7 +509,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:support-annotations:24.2.0
+com.android.support:support-annotations:24.2.1
</pre>
@@ -527,7 +527,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:design:24.2.0
+com.android.support:design:24.2.1
</pre>
@@ -548,7 +548,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:customtabs:24.2.0
+com.android.support:customtabs:24.2.1
</pre>
@@ -572,7 +572,7 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:percent:24.2.0
+com.android.support:percent:24.2.1
</pre>
@@ -595,5 +595,5 @@
<p>The Gradle build script dependency identifier for this library is as follows:</p>
<pre>
-com.android.support:recommendation:24.2.0
+com.android.support:recommendation:24.2.1
</pre>
diff --git a/docs/html/topic/libraries/support-library/revisions.jd b/docs/html/topic/libraries/support-library/revisions.jd
index 1fe4daa..9a24d15 100644
--- a/docs/html/topic/libraries/support-library/revisions.jd
+++ b/docs/html/topic/libraries/support-library/revisions.jd
@@ -6,9 +6,71 @@
<p>This page provides details about the Support Library package releases.</p>
<div class="toggle-content opened">
- <p id="rev24-2-0">
+ <p id="rev24-2-1">
<a href="#" onclick="return toggleContent(this)"><img src=
"{@docRoot}assets/images/styles/disclosure_up.png" class=
+ "toggle-content-img" alt="">Android Support Library, revision 24.2.1</a>
+ <em>(September 2016)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+
+ <p>Fixed issues:</p>
+
+<ul>
+ <li>{@link android.support.design.widget.FloatingActionButton} can no longer
+ be anchored to indirect children of {@link
+ android.support.design.widget.CoordinatorLayout}. (AOSP issue <a href=
+ "https://code.google.com/p/android/issues/detail?id=220250">220250</a>)
+ </li>
+
+ <li>Image inside {@link
+ android.support.design.widget.CollapsingToolbarLayout} doesn’t scale properly
+ with <code>fitsSystemWindows=true</code>. (AOSP issue <a href=
+ "https://code.google.com/p/android/issues/detail?id=220389">220389</a>)
+ </li>
+
+ <li>{@link android.support.design.widget.CoordinatorLayout} throws {@link
+ java.lang.IndexOutOfBoundsException} when {@link
+ android.support.design.widget.Snackbar} is shown and dismissed. (AOSP issue
+ <a href="https://code.google.com/p/android/issues/detail?id=220762"
+ >220762</a>)
+ </li>
+
+ <li>{@link android.support.design.widget.TextInputLayout} fails to resolve
+ error text color. (AOSP issue <a href=
+ "https://code.google.com/p/android/issues/detail?id=220305">220305</a>)
+ </li>
+
+ <li>{@link android.support.v7.util.SortedList.BatchedCallback#onMoved
+ BatchedCallback.onMoved()} calls {@link
+ android.support.v7.util.SortedList.BatchedCallback#onInserted
+ BatchedCallback.onInserted()}. (AOSP issue <a href=
+ "https://code.google.com/p/android/issues/detail?id=220309">220309</a>)
+ </li>
+
+ <li>{@link android.support.design.widget.TextInputLayout} overrides right
+ compound drawable. (AOSP issue <a href=
+ "https://code.google.com/p/android/issues/detail?id=220728">220728</a>)
+ </li>
+</ul>
+
+<p>
+ A complete list of public bug fixes is available on the <a href=
+ "https://code.google.com/p/android/issues/list?can=1&q=label%3ATarget-Support-24.2.1">
+ AOSP Issue Tracker</a>.
+</p>
+
+
+ </div>
+</div>
+
+<!-- end of collapsible section: 24.2.1 -->
+
+<div class="toggle-content closed">
+ <p id="rev24-2-0">
+ <a href="#" onclick="return toggleContent(this)"><img src=
+ "{@docRoot}assets/images/styles/disclosure_down.png" class=
"toggle-content-img" alt="">Android Support Library, revision 24.2.0</a>
<em>(August 2016)</em>
</p>
@@ -197,8 +259,17 @@
<li>{@link android.support.design.widget.Snackbar} now draws behind the
navigation bar if the status bar is translucent.
</li>
+
</ul>
+<h4>MediaRouter library</h4>
+
+<p>
+ Bluetooth devices are no longer listed as media routes. Routing audio to
+ Bluetooth devices is now solely controlled at the Android system level.
+</p>
+
+
<h3 id="24-2-0-deprecations">Deprecations</h3>
<p>Deprecated classes and methods are subject to removal in a future release. You should migrate away from these APIs as soon as possible.</p>
@@ -2903,8 +2974,6 @@
<ul>
<li>Added {@link android.support.v7.widget.GridLayout} to provide support for the
{@link android.widget.GridLayout} layout object.</li>
- <li>Added {@link android.support.v7.widget.Space} which can be used to create blank areas
- within a {@link android.support.v7.widget.GridLayout} layout object.</li>
</ul>
</dl>
</div>
diff --git a/docs/html/topic/libraries/testing-support-library/index.jd b/docs/html/topic/libraries/testing-support-library/index.jd
index 941f5c3..3d3c091 100644
--- a/docs/html/topic/libraries/testing-support-library/index.jd
+++ b/docs/html/topic/libraries/testing-support-library/index.jd
@@ -202,7 +202,7 @@
<li><a href="{@docRoot}reference/android/support/test/filters/SdkSuppress.html">{@code @SdkSupress}</a>:
Suppresses the test from running on a lower Android API level than the given level. For
example, to suppress tests on all API levels lower than 18 from running, use the annotation
- {@code @SDKSupress(minSdkVersion=18)}.
+ {@code @SdkSuppress(minSdkVersion=18)}.
</li>
<li>{@link android.test.suitebuilder.annotation.SmallTest @SmallTest},
diff --git a/docs/html/training/_book.yaml b/docs/html/training/_book.yaml
index 891574f..e9635be 100644
--- a/docs/html/training/_book.yaml
+++ b/docs/html/training/_book.yaml
@@ -695,6 +695,8 @@
value: 再生中カードを表示する
- title: Adding a Guided Step
path: /training/tv/playback/guided-step.html
+ - title: Introducing First-time Users to Your App
+ path: /training/tv/playback/onboarding.html
- title: Enabling Background Playback
path: /training/tv/playback/options.html
- title: Adding Picture-in-picture
@@ -898,6 +900,11 @@
value: 順応性のある UI フローの実装
- name: zh-cn-lang
value: 实施自适应用户界面流程
+ - title: Build a Responsive UI with ConstraintLayout
+ path: /training/constraint-layout/index.html
+ path_attributes:
+ - name: description
+ value: How to build a layout using ConstraintLayout and the Android Studio Layout Editor.
- title: Adding the App Bar
path: /training/appbar/index.html
path_attributes:
@@ -1149,6 +1156,8 @@
value: 维护兼容性
- name: zh-tw-lang
value: 維持相容性
+ - title: Selecting Colors with the Palette API
+ path: /training/material/palette-colors.html
- title: Best Practices for User Input
path: /training/best-user-input.html
diff --git a/docs/html/training/accessibility/service.jd b/docs/html/training/accessibility/service.jd
index de00db7..25be81e 100755
--- a/docs/html/training/accessibility/service.jd
+++ b/docs/html/training/accessibility/service.jd
@@ -17,7 +17,7 @@
<li><a href="#create">Create Your Accessibility Service</a></li>
<li><a href="#configure">Configure Your Accessibility Service</a></li>
<li><a href="#events">Respond to AccessibilityEvents</a></li>
- <li><a href="#query">Query the View Heirarchy for More Context</a></li>
+ <li><a href="#query">Query the View Hierarchy for More Context</a></li>
</ol>
<h2>You should also read</h2>
@@ -200,7 +200,7 @@
}
</pre>
-<h2 id="query">Query the View Heirarchy for More Context</h2>
+<h2 id="query">Query the View Hierarchy's for More Context</h2>
<p>This step is optional, but highly useful. The Android platform provides the ability for an
{@link android.accessibilityservice.AccessibilityService} to query the view
hierarchy, collecting information about the UI component that generated an event, and
diff --git a/docs/html/training/basics/firstapp/building-ui.jd b/docs/html/training/basics/firstapp/building-ui.jd
index a680c73..6f321e9 100644
--- a/docs/html/training/basics/firstapp/building-ui.jd
+++ b/docs/html/training/basics/firstapp/building-ui.jd
@@ -71,17 +71,17 @@
<h2 id="LinearLayout">Create a Linear Layout</h2>
<ol>
- <li>From the <code>res/layout/</code> directory, open the
- <code>activity_main.xml</code> file.
+ <li>In Android Studio's <b>Project</b> window, open <b>app > res >
+ layout > activity_main.xml</b>.
<p>This XML file defines the layout of your activity. It contains the
default "Hello World" text view.</p>
</li>
<li>When you open a layout file, you’re first shown the design editor in the
<a href="/studio/write/layout-editor.html">Layout Editor</a>. For this lesson,
- you work directly with the XML, so click the <b>Text</b> tab to switch to
- the text editor.
+ you work directly with the XML, so click the <b>Text</b> tab at the bottom
+ of the window to switch to the text editor.
</li>
- <li>Replace the contents of the file with the following XML:
+ <li>Delete everything and insert the following XML:
<pre><?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
@@ -138,6 +138,9 @@
</LinearLayout>
</pre>
+<p>Don't worry about the error that appears for
+<code>@string/edit_message</code>; you'll fix that soon.</p>
+
<p>Here is a description of the attributes in the
{@link android.widget.EditText <EditText>} you added:</p>
@@ -157,7 +160,7 @@
<p>A resource object is a unique integer name that's associated with an app resource,
such as a bitmap, layout file, or string.</p>
<p>Every resource has a
-corresponding resource object defined in your project's {@code gen/R.java} file. You can use the
+corresponding resource object defined in your project's {@code R.java} file. You can use the
object names in the {@code R} class to refer to your resources, such as when you need to specify a
string value for the <a
href="{@docRoot}reference/android/widget/TextView.html#attr_android:hint">{@code android:hint}</a>
@@ -174,7 +177,7 @@
<p>The plus sign (<code>+</code>) before the resource type is needed only when you're defining a
resource ID for the first time. When you compile the app,
the SDK tools use the ID name to create a new resource ID in
-your project's {@code gen/R.java} file that refers to the {@link
+your project's {@code R.java} file that refers to the {@link
android.widget.EditText} element. With the resource ID declared once this way,
other references to the ID do not
need the plus sign. Using the plus sign is necessary only when specifying a new resource ID and not
@@ -211,10 +214,10 @@
<h2 id="Strings">Add String Resources</h2>
<p>By default, your Android project includes a string resource file at
-<code>res/values/strings.xml</code>. Here, you'll add two new strings.</p>
+<b>res > values > strings.xml</b>. Here, you'll add two new strings.</p>
<ol>
-<li>From the <code>res/values/</code> directory, open <code>strings.xml</code>.</li>
+<li>From the <b>Project</b> window, open <b>res > values > strings.xml</b>.</li>
<li>Add two strings so that your file looks like this:
<pre><?xml version="1.0" encoding="utf-8"?>
<resources>
@@ -340,15 +343,12 @@
<h2>Run Your App</h2>
-<p>This layout is applied by the default {@link android.app.Activity} class
-that the SDK tools generated when you created the project.</p>
-
-<p>To run the app and see the results,
- click <strong>Run 'app'</strong>
+<p>To see how the app now looks on your device or emulator,
+ click <strong>Run</strong>
<img src="{@docRoot}images/tools/as-run.png"
style="vertical-align:baseline;margin:0; max-height:1em" /> in the
toolbar.</p>
-<p>Continue to the <a href="starting-activity.html">next
-lesson</a> to learn how to respond to button presses, read content
-from the text field, start another activity, and more.</p>
\ No newline at end of file
+<p>To add app behaviors such as responding to a button and starting
+another activity, continue to the <a href="starting-activity.html">next
+lesson</a>.</p>
\ No newline at end of file
diff --git a/docs/html/training/basics/firstapp/creating-project.jd b/docs/html/training/basics/firstapp/creating-project.jd
index cad32bf..60be5f6 100644
--- a/docs/html/training/basics/firstapp/creating-project.jd
+++ b/docs/html/training/basics/firstapp/creating-project.jd
@@ -31,129 +31,71 @@
<ol>
<li>In Android Studio, create a new project:
<ul>
- <li>If you don't have a project opened, in the <strong>Welcome</strong> screen, click <strong>
- New Project</strong>.</li>
- <li>If you have a project opened, from the <strong>File</strong> menu, select <strong>New
- Project</strong>. The <em>Create New Project</em> screen appears.</li>
+ <li>If you don't have a project opened, in the <strong>Welcome to Android Studio</strong> window, click <strong>
+ Start a new Android Studio project</strong>.</li>
+ <li>If you have a project opened, select <strong>File > New Project</strong>.</li>
</ul>
</li>
- <li>Fill out the fields on the screen. For <strong>Application Name</strong>
- use "My First App". For <strong>Company Domain</strong>, use "example.com".
- For the other fields, use the default values and click <strong>Next</strong>
- <p>Here's a brief explanation of each field:</p>
+ <li>In the <b>New Project</b> screen, enter the following values:</p>
<ul>
- <li><strong>Application Name</strong> is the app name that appears to users.</li>
- <li><strong>Company domain</strong> provides a qualifier that will be appended to the package
- name; Android Studio will remember this qualifier for each new project you create.</li>
- <li><strong>Package name</strong> is the fully qualified name for the project (following the
- same rules as those for naming packages in the Java programming language). Your package name
- must be unique across all packages installed on the Android system. You can <strong>
- Edit</strong> this value independently from the application name or the company
- domain.</li>
- <li><strong>Project location</strong> is the directory on your system that holds the project
- files.</li>
+ <li><strong>Application Name</strong>: "My First App" </li>
+ <li><strong>Company Domain</strong>: "example.com"</li>
</ul>
+ <p>Android Studio fills in the package name and project location for you,
+ but you can edit these if you'd like.
</li>
- <li>Under <strong>Target Android Devices</strong>, accept the default values
- and click <strong>Next</strong>.
- <p>The Minimum Required SDK is the earliest version of Android that your app supports,
- indicated using the <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">
+ <li>Click <b>Next</b>.</li>
+ <li>In the <b>Target Android Devices</b> screen, keep the default values and
+ click <b>Next</b>.
+ <p>The <b>Minimum Required SDK</b> is the earliest version of Android that your app supports,
+ which is indicated by the <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">
API level</a>. To support as many devices as possible, you should set this to the lowest
version available that allows your app to provide its core feature set. If any feature of your
- app is possible only on newer versions of Android and it's not critical to the app's core
- feature set, you can enable the feature only when running on the versions that support it (as
- discussed in <a href="{@docRoot}training/basics/supporting-devices/platforms.html">
+ app is possible only on newer versions of Android and it's not critical to the core
+ feature set, enable that feature only when running on the versions that support it (see
+ <a href="{@docRoot}training/basics/supporting-devices/platforms.html">
Supporting Different Platform Versions</a>).</p>
</li>
- <li>Under <strong>Add an Activity to Mobile</strong>, select <strong>Empty
+ <li>In the <strong>Add an Activity to Mobile</strong> screen, select <strong>Empty
Activity</strong> and click <strong>Next</strong>.
</li>
- <div class="sidebox-wrapper">
- <div class="sidebox">
- <h3>Activities</h3>
- <p>An activity is one of the distinguishing features of the Android framework. Activities
- provide the user with access to your app, and there may be many activities. An application
- will usually have a main activity for when the user launches the application, another
- activity for when she selects some content to view, for example, and other activities for
- when she performs other tasks within the app. See <a href="{@docRoot}guide/components/activities.html">
- Activities</a> for more information.</p>
- </div>
- </div>
-
- <li>Under <strong>Customize the Activity</strong>, accept the default values
+ <li>In the <strong>Customize the Activity</strong> screen, keep the default values
and click <strong>Finish</strong>.
</ol>
-<p>Your Android project is now a basic "Hello World" app that contains some default files. Take a
-moment to review the most important of these:</p>
+<p>After some processing, Android Studio opens and displays a "Hello World" app
+with default files. You will add functionality to some of
+these files in the following lessons.</p>
+
+<p>Now take a moment to review the most important files. First, be sure that
+the <b>Project</b> window is open (select <b>View > Tool Windows > Project</b>)
+and the <b>Android</b> view is selected from the drop-down list at the top.
+You can then see the following files:</p>
<dl>
- <dt><code>app/src/main/java/com.example.myfirstapp/MainActivity.java</code></dt>
+ <dt><b>app > java > com.example.myfirstapp > MainActivity.java</b></dt>
<dd>This file appears in Android Studio after the New Project wizard finishes.
It contains the class definition for the activity you created earlier. When you build
and run the app, the {@link android.app.Activity} starts and loads the
layout file that says "Hello World!"</dd>
- <dt><code>app/src/main/res/layout/activity_main.xml</code></dt>
+ <dt><b>app > res > layout > activity_main.xml</b></dt>
<dd>This XML file defines the layout of the activity. It contains a {@code TextView}
element with the text "Hello world!".</dd>
- <dt><code>app/src/main/AndroidManifest.xml</code></dt>
+ <dt><b>app > manifests > AndroidManifest.xml</b></dt>
<dd>The <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest file</a> describes
the fundamental characteristics of the app and defines each of its components. You'll revisit
this file as you follow these lessons and add more components to your app.</dd>
- <dt><code>app/build.gradle</code></dt>
+
+ <dt><b>Gradle Scripts > build.gradle</b></dt>
<dd>Android Studio uses Gradle to compile and build your app. There is a <code>build.gradle</code>
file for each module of your project, as well as a <code>build.gradle</code> file for the entire
- project. Usually, you're only interested in the <code>build.gradle</code> file for the module,
- in this case the <code>app</code> or application module. This is where your app's build dependencies
- are set, including the <code>defaultConfig</code> settings:
- <ul>
- <li><code>compiledSdkVersion</code> is the platform version against which you will compile
- your app. By default, this is set to the latest version of Android available in your SDK.
- By default, this is set to the latest version of Android SDK installed on your
- development machine.
- You can still build your app to support older versions, but setting this to the latest
- version allows you to enable new features and optimize your app for a great user experience
- on the latest devices.</li>
- <li><code>applicationId</code> is the fully qualified package name for your application that
- you specified in the New Project wizard.</li>
- <li><code>minSdkVersion</code> is the Minimum SDK version you specified during the New Project
- wizard. This is the earliest version of the Android SDK that your app supports.</li>
- <li><code>targetSdkVersion</code> indicates the highest version of Android with which you have
- tested your application. As new versions of Android become available, you should
- test your app on the new version and update this value to match the latest API level and
- thereby take advantage of new platform features. For more information, read
- <a href="{@docRoot}training/basics/supporting-devices/platforms.html">Supporting Different
- Platform Versions</a>.</li>
- </ul>
- <p>See <a href="{@docRoot}studio/build/index.html">Building Your Project with Gradle</a>
- for more information about Gradle.</p></dd>
-</dl>
-
-<p>Note also the <code>/res</code> subdirectories that contain the
-<a href="{@docRoot}guide/topics/resources/overview.html">resources</a> for your application:</p>
-<dl>
- <dt><code>drawable<em>-<density></em>/</code></dt>
- <dd>Directories for <a href="{@docRoot}guide/topics/resources/drawable-resource.html">
- drawable resources</a>, other than launcher icons, designed
- for various <a href="{@docRoot}training/multiscreen/screendensities.html">densities</a>.
-</dd>
- <dt><code>layout/</code></dt>
- <dd>Directory for files that define your app's user interface like {@code activity_main.xml},
- discussed above, which describes a basic layout for the {@code MainActivity}
- class.</dd>
- <dt><code>menu/</code></dt>
- <dd>Directory for files that define your app's menu items.</dd>
- <dt><code>mipmap/</code></dt>
- <dd>Launcher icons reside in the {@code mipmap/} folder rather than the
- {@code drawable/} folders. This folder contains the {@code ic_launcher.png} image
- that appears when you run the default app.</dd>
- <dt><code>values/</code></dt>
- <dd>Directory for other XML files that contain a collection of resources, such as
- string and color definitions.</dd>
+ project. Usually, you're only interested in the <code>build.gradle</code> file for the module.
+ in this case the <code>app</code> or application module. For more information about this file,
+ see <a href="{@docRoot}studio/build/index.html">Building Your Project with Gradle</a>.</dd>
</dl>
<p>
diff --git a/docs/html/training/basics/firstapp/running-app.jd b/docs/html/training/basics/firstapp/running-app.jd
index e809871..085849f 100755
--- a/docs/html/training/basics/firstapp/running-app.jd
+++ b/docs/html/training/basics/firstapp/running-app.jd
@@ -3,9 +3,7 @@
parent.link=index.html
trainingnavtop=true
-
page.tags=emulator
-helpoutsWidget=true
@jd:body
@@ -18,7 +16,7 @@
<ol>
<li><a href="#RealDevice">Run on a Real Device</a></li>
- <li><a href="#Emulator">Run on the Emulator</a></li>
+ <li><a href="#Emulator">Run on an Emulator</a></li>
</ol>
<h2>You should also read</h2>
@@ -34,8 +32,10 @@
<p>In the <a href="creating-project.html">previous lesson</a>, you created an
- Android project. The project contains a default app that displays
- "Hello World". In this lesson, you will run the app on a device or emulator.</p>
+Android project that displays "Hello World." You can now run the app on a real
+device or on an emulator. If you don't have a real device available, skip to
+<a href="#Emulator">Run on an Emulator</a>.</p>
+
<h2 id="RealDevice">Run on a Real Device</h2>
@@ -68,7 +68,7 @@
<p>Android Studio installs the app on your connected device and starts it.</p>
-<h2 id="Emulator">Run on the Emulator</h2>
+<h2 id="Emulator">Run on an Emulator</h2>
<p>Before you run your app on an emulator, you need to create an
<a href="{@docRoot}tools/devices/index.html">Android Virtual Device</a> (AVD)
@@ -82,12 +82,14 @@
<strong>Tools > Android > AVD Manager</strong>, or by clicking
the AVD Manager icon <img src="{@docRoot}images/tools/avd-manager-studio.png"
style="vertical-align:bottom;margin:0;height:19px"> in the toolbar.</li>
- <li>On the AVD Manager main screen, click <strong>Create Virtual Device</strong>.</li>
- <li>In the Select Hardware page, select a phone device, such as Nexus 6,
- then click <strong>Next</strong>.
+ <li>In the <b>Your Virtual Devices</b> screen, click <strong>Create Virtual Device</strong>.</li>
+ <li>In the <b>Select Hardware</b> screen, select a phone device, such as Nexus 6,
+ and then click <strong>Next</strong>.
</li>
- <li>In the Select Image page, choose the desired system image for the AVD and
+ <li>In the <b>System Image</b> screen, choose the desired system image for the AVD and
click <strong>Next</strong>.
+ <p>If you don't have a particular system image installed,
+ you can get it by clicking the <b>download</b> link.</p>
</li>
<li>Verify the configuration settings (for your first AVD, leave all the
settings as they are), and then click <strong>Finish</strong>.
diff --git a/docs/html/training/basics/firstapp/starting-activity.jd b/docs/html/training/basics/firstapp/starting-activity.jd
index ebf42cb..4385d13 100644
--- a/docs/html/training/basics/firstapp/starting-activity.jd
+++ b/docs/html/training/basics/firstapp/starting-activity.jd
@@ -38,7 +38,7 @@
<h2 id="RespondToButton">Respond to the Send Button</h2>
<ol>
- <li>In the file <code>res/layout/activity_main.xml</code>, add the
+ <li>In the file <b>res > layout > activity_main.xml</b>, add the
<a href="{@docRoot}reference/android/view/View.html#attr_android:onClick">{@code android:onClick}</a>
attribute to the {@link android.widget.Button <Button>} element as
shown below:
@@ -52,7 +52,7 @@
method in your activity whenever a user clicks on the button.</p>
</li>
- <li>In the file <code>java/com.example.myfirstapp/MainActivity.java</code>,
+ <li>In the file <b>java > com.example.myfirstapp > MainActivity.java</b>,
add the <code>sendMessage()</code> method stub as shown below:
<pre>public class MainActivity extends AppCompatActivity {
@@ -85,7 +85,9 @@
<p>Next, you’ll fill in this method to read the contents of the text field and deliver that text to
another activity.</p>
+
<h2 id="BuildIntent">Build an Intent</h2>
+
<p>An {@link android.content.Intent} is an object that provides runtime binding
between separate components (such as two activities). The
{@link android.content.Intent} represents an app’s "intent to do something."
@@ -113,13 +115,22 @@
}
}</pre>
-<p class="note"><strong>Note: </strong>Android Studio will display
- <code>Cannot resolve symbol</code> errors because the code references classes
- like {@link android.content.Intent} and {@link android.widget.EditText}
- that have not been imported. To import these classes, you can either 1)
- use Android Studio's "import class" functionality by pressing Alt + Enter
- (Option + Return on Mac) or 2) manually add import statements at the top of
- the file.</p>
+<p>Android Studio will display <b>Cannot
+resolve symbol</b> errors because this code references classes that are not
+imported. You can solve some of these with Android Studio's "import class"
+functionality by pressing Alt + Enter (or Option + Return on Mac).
+Your imports should end up as the following:</p>
+<pre>
+import android.content.Intent;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.EditText;
+</pre>
+
+<p>An error remains for <code>DisplayMessageActivity</code>, but that's okay;
+you'll fix that in the next section.
+
<p>There’s a lot going on in <code>sendMessage()</code>, so let’s explain
what's going on.</p>
@@ -150,6 +161,7 @@
method starts an instance of the <code>DisplayMessageActivity</code> specified
by the {@link android.content.Intent}. Now you need to create the class.</p>
+
<h2 id="CreateActivity">Create the Second Activity</h2>
<ol>
@@ -169,7 +181,8 @@
<li>Creates the corresponding layout file <code>activity_display_message.xml</code>
</li>
<li>Adds the required
- <a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a>
+ <a href="{@docRoot}guide/topics/manifest/activity-element.html"
+ ><code><activity></code></a>
element in <code>AndroidManifest.xml</code>.
</ul>
@@ -199,7 +212,16 @@
layout.addView(textView);
}</pre>
</li>
- <li>Press Alt + Enter (option + return on Mac) to import missing classes.</li>
+ <li>Press Alt + Enter (or Option + Return on Mac) to import missing classes.
+ Your imports should end up as the following:
+<pre>
+import android.content.Intent;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.TextView;
+</pre>
+</li>
</ol>
<p>There’s a lot going on here, so let’s explain:</p>
diff --git a/docs/html/training/constraint-layout/images/alignment-constraint-offset_2x.png b/docs/html/training/constraint-layout/images/alignment-constraint-offset_2x.png
new file mode 100644
index 0000000..1e4867e
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/alignment-constraint-offset_2x.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/alignment-constraint_2x.png b/docs/html/training/constraint-layout/images/alignment-constraint_2x.png
new file mode 100644
index 0000000..afe7d4a
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/alignment-constraint_2x.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/baseline-constraint_2x.png b/docs/html/training/constraint-layout/images/baseline-constraint_2x.png
new file mode 100644
index 0000000..dfc3522
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/baseline-constraint_2x.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/constraint-fail-fixed_2x.png b/docs/html/training/constraint-layout/images/constraint-fail-fixed_2x.png
new file mode 100644
index 0000000..be9d54f
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/constraint-fail-fixed_2x.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/constraint-fail_2x.png b/docs/html/training/constraint-layout/images/constraint-fail_2x.png
new file mode 100644
index 0000000..3f28ef7
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/constraint-fail_2x.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/layout-editor-convert-to-constraint_2x.png b/docs/html/training/constraint-layout/images/layout-editor-convert-to-constraint_2x.png
new file mode 100644
index 0000000..ace31a6
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/layout-editor-convert-to-constraint_2x.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/layout-editor-margin-callout_2-2_2x.png b/docs/html/training/constraint-layout/images/layout-editor-margin-callout_2-2_2x.png
new file mode 100644
index 0000000..0768022
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/layout-editor-margin-callout_2-2_2x.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/layout-editor-properties-callouts_2-2_2x.png b/docs/html/training/constraint-layout/images/layout-editor-properties-callouts_2-2_2x.png
new file mode 100644
index 0000000..b4ffb2c
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/layout-editor-properties-callouts_2-2_2x.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/layout-editor_2-2_2x.png b/docs/html/training/constraint-layout/images/layout-editor_2-2_2x.png
new file mode 100644
index 0000000..72a4e40
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/layout-editor_2-2_2x.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/parent-constraint_2x.png b/docs/html/training/constraint-layout/images/parent-constraint_2x.png
new file mode 100644
index 0000000..0414f1d
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/parent-constraint_2x.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/position-constraint_2x.png b/docs/html/training/constraint-layout/images/position-constraint_2x.png
new file mode 100644
index 0000000..9f93e72
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/position-constraint_2x.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/thumbnail-add-layout-guideline_2-2.png b/docs/html/training/constraint-layout/images/thumbnail-add-layout-guideline_2-2.png
new file mode 100644
index 0000000..f863e5f
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/thumbnail-add-layout-guideline_2-2.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/thumbnail-adjust-constraint-bias.png b/docs/html/training/constraint-layout/images/thumbnail-adjust-constraint-bias.png
new file mode 100644
index 0000000..d61e9b2
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/thumbnail-adjust-constraint-bias.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/thumbnail-studio-constraint-first.png b/docs/html/training/constraint-layout/images/thumbnail-studio-constraint-first.png
new file mode 100644
index 0000000..9747102
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/thumbnail-studio-constraint-first.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/images/thumbnail-studio-constraint-second.png b/docs/html/training/constraint-layout/images/thumbnail-studio-constraint-second.png
new file mode 100644
index 0000000..940b8495
--- /dev/null
+++ b/docs/html/training/constraint-layout/images/thumbnail-studio-constraint-second.png
Binary files differ
diff --git a/docs/html/training/constraint-layout/index.html b/docs/html/training/constraint-layout/index.html
new file mode 100644
index 0000000..62eaf15
--- /dev/null
+++ b/docs/html/training/constraint-layout/index.html
@@ -0,0 +1,498 @@
+<html devsite>
+<head>
+ <title>Build a Responsive UI with ConstraintLayout</title>
+ <meta name="book_path" value="/training/_book.yaml" />
+ <meta name="top_category" value="develop" />
+ <meta name="subcategory" value="training" />
+</head>
+<body>
+
+<div id="tb-wrapper">
+<div id="tb">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#constraints-overview">Constraints overview</a></li>
+ <li><a href="#add-constraintlayout-to-your-project">Add ConstraintLayout to your project</a></li>
+ <li><a href="#add-a-constraint">Add a constraint</a></li>
+ <li><a href="#use-autoconnect-and-infer-constraints">Use Autoconnect and Infer Constraints</a></li>
+ <li><a href="#adjust-the-view-size">Adjust the view size</a></li>
+ <li><a href="#adjust-the-constraint-bias">Adjust the constraint bias</a></li>
+ <li><a href="#adjust-the-view-margins">Adjust the view margins</a></li>
+ </ol>
+</div>
+</div>
+
+
+<p><code>ConstraintLayout</code> allows you to create large and complex layouts with a flat view
+hierarchy (no nested view groups). It's similar to <code>RelativeLayout</code> in that all views are
+layed out according to relationships between sibling views and the parent layout, but it's more
+flexible than <code>RelativeLayout</code> and easier to use with Android Studio's Layout Editor.
+</p>
+
+<p>Everything you can do with <code>ConstraintLayout</code> is available directly from the Layout Editor's visual
+tools, because the layout API and the Layout Editor were specially built for each other. So you can
+build your layout with <code>ConstraintLayout</code> entirely by drag-and-dropping instead of editing the XML.
+</p>
+
+<img src="/training/constraint-layout/images/layout-editor_2-2_2x.png" alt=""
+ width="640"/>
+<p class="img-caption"><b>Figure 1.</b> A <code>ConstraintLayout</code> in the Layout Editor</p>
+
+
+<p>
+<code>ConstraintLayout</code> is available in an API library that's compatible with Android
+2.3 (API level 9) and higher, and the new layout editor is available in Android
+Studio 2.2 and higher.
+</p>
+
+<p>
+This page provides a guide to building a layout with <code>ConstraintLayout</code> in Android
+Studio. If you'd like more information about the Layout Editor itself, see the
+Android Studio guide to <a href="/studio/write/layout-editor.html">Build a UI with
+Layout Editor</a>.
+</p>
+
+
+<h2 id="constraints-overview">Constraints overview</h2>
+<p>
+To define a view's position in <code>ConstraintLayout</code>, you must add two
+or more <em>constraints</em> for the view. Each constraint represents a connection or
+alignment to another view, the parent layout, or an invisible guideline. Each
+constraint defines the view's position along either the
+vertical or horizontal axis; so each view must have a minimum of one constraint for each
+axis, but often more are necessary.
+</p>
+
+<p>
+When you drop a view into the Layout Editor, it stays where you leave it even if
+it has no constraints. However, this is only to make editing easier; if a view has
+no constraints when you run your layout on a device, it is drawn at
+position [0,0] (the top-left corner).</p>
+
+<p>In figure 2, the layout looks good in the
+editor, but there's no vertical constraint on <code>TextView B</code>. When this
+layout draws on a device, <code>TextView B</code> horizontally aligns with the left and
+right edges of the <code>ImageView</code>, but appears at the top of the screen because
+it has no vertical constraint.
+</p>
+
+<div class="cols">
+<div class="col-1of2">
+<img src="/training/constraint-layout/images/constraint-fail_2x.png" width="100%" alt="" />
+<p class="img-caption"><strong>Figure 2.</strong> <code>TextView B</code> is missing a
+vertical constraint</p>
+</div>
+<div class="col-1of2">
+<img src="/training/constraint-layout/images/constraint-fail-fixed_2x.png" width="100%" alt="" />
+<p class="img-caption"><strong>Figure 3.</strong> <code>TextView B</code> is now vertically
+constrained to the <code>ImageView</code></p>
+</div>
+</div>
+
+<p>
+Although a missing constraint won't cause a compilation error, the Layout Editor
+indicates missing constraints as an error in the toolbar. To view the errors and
+other warnings, click <strong>Show Warnings and Errors</strong>
+<img src="/studio/images/buttons/layout-editor-errors.png" class="inline-icon" alt="" />.
+To help you avoid missing constraints, the Layout Editor can automatically add
+constraints for you with the <a
+href="#use-autoconnect-and-infer-constraints">Autoconnect and infer
+constraints</a> features.
+</p>
+
+
+<h2 id="add-constraintlayout-to-your-project">Add ConstraintLayout to your project</h2>
+<p>
+To use <code>ConstraintLayout</code> in your project, proceed as follows:
+</p>
+
+<ol>
+<li>Ensure you have the latest Constraint Layout library:
+<ol>
+ <li>Click <strong>Tools > Android > SDK Manager</strong>.
+ <li>Click the <strong>SDK Tools</strong> tab.
+ <li>Expand <strong>Support Repository</strong> and then check
+<b>ConstraintLayout for Android</b> and <b>Solver for ConstraintLayout</b>.
+Check <b>Show Package Details</b> and take note of the version you're downloading
+(you'll need this below).</p>
+ <li>Click <strong>OK</strong>.
+<li>Add the ConstraintLayout library as a dependency in your module-level
+ <code>build.gradle</code> file:
+<pre>
+dependencies {
+ compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha8'
+}
+</pre>
+ <p>The library version you download may be higher, so be sure the value you specify
+ here matches the version from step 3.</p>
+</li>
+<li>In the toolbar or sync notification, click <strong>Sync Project with Gradle
+Files</strong>.</li>
+</ol>
+</li>
+</ol>
+
+<p>Now you're ready to build your layout with <code>ConstraintLayout</code>.</p>
+
+<h3 id="convert">Convert a layout</h3>
+
+<div class="figure" style="width:415px">
+<img src="/training/constraint-layout/images/layout-editor-convert-to-constraint_2x.png"
+ width="415" alt="" />
+<p class="img-caption">
+ <b>Figure 4.</b> The menu to convert a layout to <code>ConstraintLayout</code></p>
+</div>
+
+<p>To convert an existing layout to a constraint layout, follow these steps:</p>
+<ol>
+<li>Open your layout in Android Studio and click the <strong>Design</strong> tab
+at the bottom of the editor window.
+<li>In the <strong>Component Tree</strong> window, right-click the layout and
+click <strong>Convert <em>layout</em> to ConstraintLayout</strong>.</li>
+</ol>
+
+<h3 id="createNew">Create a new layout</h3>
+
+<p>To start a new constraint layout file, follow these steps:</p>
+<ol>
+<li>Click anywhere in the <strong>Project</strong> window and then select
+<strong>File > New > XML > Layout XML</strong>.
+<li>Enter a name for the layout file and enter
+"android.support.constraint.ConstraintLayout" for the <b>Root Tag</b>.
+<li>Click <strong>Finish</strong>.</li>
+</ol>
+
+
+<h2 id="add-a-constraint">Add a constraint</h2>
+
+<p>Start by dragging a view from the <b>Palette</b> window into the editor.
+When you add a view in a <code>ConstraintLayout</code>, it displays a bounding box with square
+resizing handles on each corner and circular constraint handles on each side.
+</p>
+
+
+<div class="figure" style="width:460px">
+<div class="video-wrapper">
+<video controls poster="/training/constraint-layout/images/thumbnail-studio-constraint-first.png"
+ onclick="this.play()" width="460">
+ <source src="https://storage.googleapis.com/androiddevelopers/videos/studio/studio-constraint-first.mp4" type="video/mp4">
+ <img src="/training/constraint-layout/images/thumbnail-studio-constraint-first" alt="" />
+</video>
+</div>
+<p class="img-caption">
+<strong>Video 1. </strong>The left side of a view is constrained to the left side of the parent
+</p>
+</div>
+
+<p>
+Click the view to select it. Then click-and-hold one of the
+constraint handles and drag the line to an available anchor point (the edge of
+another view, the edge of the layout, or a guideline). When you release, the
+constraint is made, with <a href="#adjust-the-view-margins">a default margin</a>
+separating the two views.
+</p>
+
+<p>When creating constraints, remember the following rules:</p>
+
+<ul>
+<li>Every view must have at least two constraints: one horizontal and one
+vertical.
+<li>You can create constraints only between a constraint handle and an anchor
+point that share the same plane. So a vertical plane (the left and right sides)
+of a view can be constrained only to another vertical plane; and baselines can
+constrain only to other baselines.
+<li>Each constraint handle can be used for just one constraint, but you can
+create multiple constraints (from different views) to the same anchor
+point.</li>
+</ul>
+
+
+
+<div class="figure" style="width:460px">
+<div class="video-wrapper">
+<video controls poster="/training/constraint-layout/images/thumbnail-studio-constraint-second.png"
+ onclick="this.play()" width="460">
+ <source src="https://storage.googleapis.com/androiddevelopers/videos/studio/studio-constraint-second.mp4" type="video/mp4">
+ <img src="/training/constraint-layout/images/thumbnail-studio-constraint-second.png" alt="" />
+</video>
+</div>
+<p class="img-caption">
+<strong>Video 2. </strong>Adding a constraint that opposes an existing one
+</p>
+</div>
+
+
+
+<p>
+To remove a constraint, select the view and then click the constraint handle.
+</p>
+
+<p>If you add opposing constraints on a view, the constraint lines become squiggly
+like a spring to indicate the opposing forces, as shown in video 2. The effect
+is most visible when the view size is set to "fixed" or "wrap content," in which
+case the view is centered between the constraints. If you instead
+want the view to stretch its size to meet the constraints, <a
+href="#adjust-the-view-size">switch the size to "any size"</a>; or if you want
+to keep the current size but move the view so that it is not centered, <a
+href="#adjust-the-constraint-bias">adjust the constraint bias</a>.
+</p>
+
+
+
+<p>
+There are many ways to constrain a view, but the following constraint types
+provide the basic building blocks.
+</p>
+
+
+
+
+<h3>Parent constraint</h3>
+<div class="cols">
+<div class="col-2of3">
+ <p>
+ Connect the side of a view to the corresponding edge of the layout.
+ <p>
+ In figure 5, the left side of a view is connected to the left edge of the
+ parent layout.
+ <p>
+</div>
+<div class="col-1of3">
+ <img src="/training/constraint-layout/images/parent-constraint_2x.png" width="100%" alt="">
+ <p class="img-caption"><strong>Figure 5. </strong>A horizontal constraint to the parent</p>
+</div>
+</div>
+
+
+<h3>Position constraint</h3>
+<div class="cols">
+<div class="col-2of3">
+<p>Define the order of appearance for two views, either vertically or horizontally.</p>
+<p>In figure 6, a <code>Button</code> is constrained below an <code>ImageView</code> with a 24dp
+margin.</p>
+</div>
+<div class="col-1of3">
+ <img src="/training/constraint-layout/images/position-constraint_2x.png" width="100%" alt="">
+ <p class="img-caption"><strong>Figure 6.</strong> A vertical position constraint</p>
+</div>
+</div>
+
+
+
+<h3>Alignment constraint</h3>
+<div class="cols">
+<div class="col-1of3">
+<p>Align the edge of a view to the same edge of another view.<p>
+<p>In figure 7, the left side of a <code>Button</code> is aligned to the left side of an
+<code>ImageView</code>.</p>
+<p>You can offset the alignment by dragging the view
+inward from the constraint. For example, figure 8 shows the same
+<code>Button</code> with a 24dp offset alignment.
+The offset is defined by the constrained view's margin.</p>
+</div>
+<div class="col-1of3">
+ <img src="/training/constraint-layout/images/alignment-constraint_2x.png" width="100%" alt="">
+ <p class="img-caption"><strong>Figure 7.</strong> A horizontal alignment constraint</p>
+</div>
+<div class="col-1of3">
+ <img src="/training/constraint-layout/images/alignment-constraint-offset_2x.png" width="100%"
+ alt="">
+ <p class="img-caption"><strong>Figure 8.</strong> An offset horizontal alignment constraint</p>
+</div>
+</div>
+
+
+<h3>Baseline alignment constraint</h3>
+<div class="cols">
+<div class="col-2of3">
+<p>Align the text baseline of a view to the text baseline of another view.</p>
+<p>In figure 9, the first line of a <code>TextView</code> is aligned with the text in a
+<code>Button</code>.</p>
+<p>To create a baseline constraint, hover your mouse over the baseline handle for
+two seconds until the handle blinks white. Then click and drag the line to
+another baseline.</p>
+</div>
+<div class="col-1of3">
+ <img src="/training/constraint-layout/images/baseline-constraint_2x.png" width="100%" alt="">
+ <p class="img-caption"><strong>Figure 9.</strong> A baseline alignment constraint</p>
+</div>
+</div>
+
+
+
+
+
+<h3 id="constrain-to-a-guideline">Constrain to a guideline</h3>
+<div class="cols">
+<div class="col-1of2">
+
+<p>
+You can add a vertical or horizontal guideline to which you can attach
+constraints. You can position the guideline within the layout based on either dp
+units or percent, relative to the layout's edge.
+</p>
+
+<p>
+To create a guideline, click <strong>Guidelines</strong>
+<img src="/studio/images/buttons/layout-editor-guidelines.png" class="inline-icon" alt="" />
+in the toolbar, and then click either <strong>Add Vertical Guideline</strong>
+or <strong>Add Horizontal Guideline</strong>.
+</p>
+
+<p>
+Click the circle at the edge of the guideline to toggle the measurements used to
+position the guideline (either percent or dp units from the layout's edge).
+</p>
+
+<p>
+Guidelines are not visible to your users.
+</p>
+</div>
+
+<div class="col-1of2">
+ <div class="video-wrapper">
+ <video controls poster="/training/constraint-layout/images/thumbnail-add-layout-guideline_2-2.png"
+ onclick="this.play()" width="100%">
+ <source src="https://storage.googleapis.com/androiddevelopers/videos/studio/add-layout-guideline_2-2.mp4" type="video/mp4">
+ <img src="/training/constraint-layout/images/thumbnail-add-layout-guideline_2-2.png" alt="" />
+ </video>
+ </div>
+ <p class="img-caption"><strong>Video 3.</strong> Adding a constraint to a guideline</p>
+</div>
+</div>
+
+
+<h2 id="use-autoconnect-and-infer-constraints">Use Autoconnect and Infer Constraints</h2>
+
+<div class="figure" style="width:460px">
+<div class="video-wrapper">
+<video controls poster=""
+ onclick="this.play()" width="460">
+ <source src="https://storage.googleapis.com/androiddevelopers/videos/studio/constraint-autoconnect_2-2.mp4" type="video/mp4">
+</video>
+</div>
+<p class="img-caption"><b>Video 4.</b> Adding a view with Autoconnect enabled</p>
+</div>
+
+<p>
+Autoconnect is a persistent mode that automatically creates two or more
+constraints for each view you add to the layout. Autoconnect is disabled by
+default. You can enable it by clicking <strong>Turn on Autoconnect</strong>
+<img src="/studio/images/buttons/layout-editor-autoconnect-on.png" class="inline-icon" alt="" />
+in the Layout Editor toolbar.
+</p>
+
+<p>While enabled, Autoconnect creates constraints for each view as you add them; it does not create
+constraints for existing views in the layout. If you drag a view once the constraints are made, the
+constraints do not change (though the margins do), so you must delete the constraints if you want to
+significantly reposition the view.</p>
+
+<p>Alternatively, you can click <strong>Infer Constraints</strong>
+<img src="/studio/images/buttons/layout-editor-infer.png" class="inline-icon" alt="" />
+to create constraints for all views in the layout.
+</p>
+
+<p>Infer Constraints is a one-time action that scans the entire layout to determine the most
+effective set of constraints for all views, so it may create constraints between elements that are
+far from each other. Autoconnect, however, creates constraints only for the view you are adding, and
+it creates constraints to only the nearest elements. In either case, you can always modify a
+constraint by clicking the constraint handle to delete it, and then create a new constraint.</p>
+
+
+<h2 id="adjust-the-view-size">Adjust the view size</h2>
+
+<p>
+You can use the handles on each corner of the view to resize it, but doing so
+hard codes the width and height values, which you should avoid for most views
+because hard-coded view sizes cannot adapt to different content and screen
+sizes. To select from one of the dynamic sizing modes or to define more specific
+dimensions, click a view and open the <strong>Properties</strong>
+<img src="/studio/images/buttons/window-properties.png" class="inline-icon" alt="" />
+window on the right side of the editor. At the top of the window is the view
+inspector, as shown in figure 10.
+</p>
+<div class="figure" style="width:287px" >
+<img src="/training/constraint-layout/images/layout-editor-properties-callouts_2-2_2x.png" alt=""
+ width="287" />
+<p class="img-caption"><strong>Figure 10.</strong> The <b>Properties</b> window includes controls for
+<strong>(1)</strong> view size, <strong>(2)</strong> margins, and
+<strong>(3)</strong> constraint bias.</p>
+</div>
+
+<p>
+The grey square represents the selected view. The symbols inside the square
+represent the height and width settings as follows:
+</p>
+
+<ul>
+<li>
+<img src="/studio/images/buttons/layout-width-wrap.png" class="inline-icon" alt="" />
+ <strong>Wrap Content</strong>: The view expands exactly as needed to fit its
+contents.
+<li>
+<img src="/studio/images/buttons/layout-width-match.png" class="inline-icon" alt="" />
+ <strong>Any Size</strong>: The view expands exactly as needed to match the
+constraints. The actual value is 0dp because the view has no desired dimensions, but
+it resizes as needed to meet the constraints. However, if the given dimension
+has only one constraint, then the view expands to fit its contents. Another way
+to think of it is "match constraints" (instead of <code>match_parent</code>) because it
+expands the view as much as possible after accounting for the limits of each
+constraint and its margins.
+<li>
+<img src="/studio/images/buttons/layout-width-fixed.png" class="inline-icon" alt="" />
+ <strong>Fixed</strong>: You specify the dimension in the text box below or by
+resizing the view in the editor.</li>
+</ul>
+
+<p>To toggle between these settings, click the symbols.</p>
+
+<p class="note"><strong>Note</strong>: You should not use <code>match_parent</code> for any view
+in a <code>ConstraintLayout</code>. Instead use "Any Size" (<code>0dp</code>).
+</p>
+
+
+<h2 id="adjust-the-constraint-bias">Adjust the constraint bias</h2>
+
+<p>When you add a constraint to both sides of a view (and the view size for the same dimension is
+either "fixed" or "wrap content"), the view becomes centered between the two anchor points by
+default. When a view is centered, the bias is 50%. You can adjust the bias by dragging the bias
+slider in the <b>Properties</b> window or by dragging the view, as shown in video 5.</p>
+
+<div class="video-wrapper" style="max-width:740px">
+<video controls poster="/training/constraint-layout/images/thumbnail-adjust-constraint-bias.png"
+ onclick="this.play();$(this.parentElement).addClass('playing');">
+ <source src="https://storage.googleapis.com/androiddevelopers/videos/studio/adjust-constraint-bias.mp4" type="video/mp4">
+ <img src="/training/constraint-layout/images/thumbnail-adjust-constraint-bias.png" alt="" />
+</video>
+</div>
+<p class="img-caption"><b>Video 5.</b> Adjusting the constraint bias</p>
+
+<p>If you instead want the view to stretch its size to meet the constraints, <a href="#adjust-the-
+view-size">switch the size to "any size"</a>.</p>
+
+
+<h2 id="adjust-the-view-margins">Adjust the view margins</h2>
+
+<p> To ensure that all your views are evenly spaced, click <strong>Margin</strong> <img
+src="/studio/images/buttons/layout-editor-margin.png" class="inline-icon" alt="" /> in the toolbar
+to select the default margin for each view that you add to the layout. The button changes to show
+your current margin selection. Any change you make to the default margin applies only to the views
+you add from then on. </p>
+
+
+<img src="/training/constraint-layout/images/layout-editor-margin-callout_2-2_2x.png"
+ alt="" width="232"/>
+<p class="img-caption"><strong>Figure 11.</strong> The toolbar's <b>Margin</b> button.
+Click to adjust the default margin.
+</p>
+
+<p> You can control the margin for each view in the <strong>Properties</strong> window by clicking
+the number on the line that represents each constraint (in figure 10, the margins are each set to
+16dp). </p>
+
+<p> All margins offered by the tool are factors of 8dp to help your views align to Material Design's
+<a href="https://material.google.com/layout/metrics-keylines.html">8dp square grid
+recommendations</a>. </p>
+
+</body>
+</html>
diff --git a/docs/html/training/material/images/palette-library-color-profiles_2-1_2x.png b/docs/html/training/material/images/palette-library-color-profiles_2-1_2x.png
new file mode 100644
index 0000000..d14ec32
--- /dev/null
+++ b/docs/html/training/material/images/palette-library-color-profiles_2-1_2x.png
Binary files differ
diff --git a/docs/html/training/material/images/palette-library-title-text-color_2-1_2x.png b/docs/html/training/material/images/palette-library-title-text-color_2-1_2x.png
new file mode 100644
index 0000000..883adba
--- /dev/null
+++ b/docs/html/training/material/images/palette-library-title-text-color_2-1_2x.png
Binary files differ
diff --git a/docs/html/training/material/index.jd b/docs/html/training/material/index.jd
index 4001e6b..8baa065 100644
--- a/docs/html/training/material/index.jd
+++ b/docs/html/training/material/index.jd
@@ -3,7 +3,6 @@
page.image=images/cards/material_2x.png
page.metaDescription=Learn how to apply material design to your apps.
-
@jd:body
<div id="tb-wrapper">
@@ -58,6 +57,9 @@
<dt><a href="{@docRoot}training/material/compatibility.html">Maintaining Compatibility</a></dt>
<dd>Learn how to maintain compatibility with platform versions earlier than Android 5.0.</dd>
+
+ <dt><a href="{@docRoot}training/material/palette-colors.html">Selecting Colors with the Palette API</a></dt>
+ <dd>Learn how to select colors for your app using the v7 Palette library.</dd>
</dl>
<h2>Video Training</h2>
diff --git a/docs/html/training/material/palette-colors.html b/docs/html/training/material/palette-colors.html
new file mode 100644
index 0000000..27485d2
--- /dev/null
+++ b/docs/html/training/material/palette-colors.html
@@ -0,0 +1,310 @@
+<html devsite>
+<head>
+ <title>Selecting Colors with the Palette API</title>
+ <meta name="book_path" value="/training/_book.yaml" />
+ <meta name="top_category" value="develop" />
+ <meta name="subcategory" value="training" />
+</head>
+<body>
+
+<div id="tb-wrapper">
+ <div id="tb">
+ <h2>This lesson teaches you to</h2>
+ <ol>
+ <li><a href="#set-up-the-library">Set up the library</a></li>
+ <li><a href="#create-a-palette">Create a palette</a>
+ <ol>
+ <li><a href="#generate-a-palette-instance">Generate a Palette instance</a></li>
+ <li><a href="#customize-your-palette">Customize your palette</a></li>
+ </ol>
+ </li>
+ <li><a href="#extract-color-profiles">Extract color profiles</a>
+ <ol>
+ <li><a href="#use-swatches">Use swatches to create color schemes</a></li>
+ </ol>
+ </li>
+ </ol>
+ <h2>You should also read</h2>
+ <ul>
+ <li><a href="http://www.google.com/design/spec">Material design specification</a></li>
+ <li><a href="/design/material/index.html">Material design on Android</a></li>
+ </ul>
+ </div>
+</div>
+
+<p>Good visual design is essential for a successful app, and color schemes are a primary component of design. The palette library is a
+<a href="/topic/libraries/support-library/features.html#v7-palette">support library</a>
+that extracts prominent colors from images to help you create visually engaging apps.</p>
+
+<p>You can use the palette library to design layout
+<a href="/guide/topics/ui/themes.html">themes</a> and apply custom colors to visual elements in your app.
+For example, you can use a palette to create a color-coordinated title
+card for a song based on its album cover or to adjust an app’s toolbar color when its
+background image changes. The <code><a
+href="/reference/android/support/v7/graphics/Palette.html">Palette</a></code> object gives
+you access to the colors in a <code><a
+href="/reference/android/graphics/Bitmap.html">Bitmap</a></code>
+image while also providing six main color profiles from the bitmap to help
+inform your <a href="http://material.google.com">design choices</a>.</p>
+
+<h2 id="set-up-the-library">Set up the library</h2>
+
+<p>To use the palette library, install or update the <a
+href="/topic/libraries/support-library/index.html">Android
+Support Library</a> to version 24.0.0 or higher and follow the instructions for <a
+href="/topic/libraries/support-library/setup.html#add-library">Adding
+Support Libraries</a> to add the palette library to your app development project.</p>
+
+<p>Make sure that the version specified in your dependency identifier matches your
+app’s <code>compileSdkVersion</code>, set in the <code>build.gradle</code>
+file:</p>
+
+<pre class="prettyprint">
+android {
+ compileSdkVersion 24
+ ...
+}
+
+dependencies {
+ ...
+ compile 'com.android.support:palette-v7:24.2.1'
+}
+</pre>
+
+<p>For more information about adding the palette dependency, read about the palette
+feature in the <a
+href="/topic/libraries/support-library/features.html#v7-palette">support
+library documentation</a>.</p>
+
+<h2 id="create-a-palette">Create a palette</h2>
+
+<p>A <code>Palette</code> object gives you access to the primary colors in an
+image, as well as the corresponding colors for overlaid text. Use palettes to design
+your app’s style and to dynamically change your app’s color scheme based on a
+given source image.</p>
+
+<p>To create a palette, first instantiate a <code><a
+href="https://developer.android.com/reference/android/support/v7/graphics/Palette.Builder.html">Palette.Builder</a></code>
+from a <code>Bitmap</code>. You can then use the
+<code>Palette.Builder</code> to customize the palette before generating it. This
+section will describe palette generation and customization from a bitmap
+image.</p>
+
+<h3 id="generate-a-palette-instance">Generate a Palette instance</h3>
+
+<p>Generate a <code>Palette</code> instance using <code>Palette</code>’s
+<code><a
+href="/reference/android/support/v7/graphics/Palette.html#from(android.graphics.Bitmap)">from(Bitmap
+bitmap)</a></code> method to first create a <code>Palette.Builder</code>
+from a <code>Bitmap</code>. The builder can then generate the palette either
+synchronously or asynchronously.</p>
+
+<p>Use synchronous palette generation if you want to create the palette on
+the same thread as the method being called. If you generate the palette
+asynchronously on a different thread, use the <code><a
+href="/reference/android/support/v7/graphics/Palette.PaletteAsyncListener.html#onGenerated(android.support.v7.graphics.Palette)">onGenerated()</a></code>
+method to access the palette immediately after it has been created.</p>
+
+<p>The following code snippet provides example methods for both types of palette generation:</p>
+
+<pre class="prettyprint">
+// Generate palette synchronously and return it
+public Palette createPaletteSync(Bitmap bitmap) {
+ Palette p = Palette.from(bitmap).generate();
+ return p;
+}
+
+// Generate palette asynchronously and use it on a different
+// thread using onGenerated()
+public void createPaletteAsync(Bitmap bitmap) {
+ Palette.from(bitmap).generate(new PaletteAsyncListener() {
+ public void onGenerated(Palette p) {
+ // Use generated instance
+ }
+ });
+}
+</pre>
+
+<p>If you need to continuously generate palettes for a sorted list of images
+or objects, consider <a
+href="/reference/android/util/LruCache.html">caching</a>
+the <code>Palette</code> instances to prevent slow UI performance. You also
+should not create the palettes on your <a href="/training/articles/perf-anr.html">main thread</a>.</p>
+
+<h3 id="customize-your-palette">Customize your palette</h3>
+
+<p>The <code>Palette.Builder</code> allows you to customize your palette by
+choosing how many colors are in the resulting palette, what area of your
+image the builder uses to generate the palette, and what colors are allowed in the
+palette. For example, you can filter out the color black or ensure that the
+builder only uses the top half of an image to generate your palette.</p>
+
+<p>Fine-tune your palette’s size and colors with the following methods from
+the <code>Palette.Builder</code> class:</p>
+
+<dl>
+
+ <dt><code><a
+ href="/reference/android/support/v7/graphics/Palette.Builder.html#addFilter(android.support.v7.graphics.Palette.Filter)">addFilter()</a></code></dt>
+ <dd>This method adds a filter that indicates what colors are allowed in the
+ resulting palette. Pass in your own<code> <a
+ href="/reference/android/support/v7/graphics/Palette.Filter.html">Palette.Filter</a></code>
+ and modify its <code>isAllowed()</code> method to determine which colors are
+ filtered from the palette.</dd>
+
+ <dt><code><a
+ href="/reference/android/support/v7/graphics/Palette.Builder.html#maximumColorCount(int)">maximumColorCount()</a></code></dt>
+ <dd>This method sets the maximum number of colors in your palette. The
+ default value is 16, and the optimal value depends on the source image.
+ For landscapes, optimal values range from 8-16 while pictures with faces
+ usually have values that fall between 24-32. The
+ <code>Palette.Builder</code> takes longer to generate palettes with more
+ colors.</dd>
+
+ <dt><code><a
+ href="/reference/android/support/v7/graphics/Palette.Builder.html#setRegion(int,%20int,%20int,%20int)">setRegion()</a></code></dt>
+ <dd>This method indicates what area of the bitmap the builder uses when
+ creating the palette. You can only use this method when generating the palette from
+ a bitmap, and it does not affect the original image.</dd>
+
+ <dt><code><a
+ href="/reference/android/support/v7/graphics/Palette.Builder.html#addTarget(android.support.v7.graphics.Target)">addTarget()</a></code></dt>
+ <dd>This method allows you to perform your own color matching by adding a
+ <code><a
+ href="/reference/android/support/v7/graphics/Target.html">Target</a></code>
+ color profile to the builder. If the default <code>Target</code>s are not
+ sufficient, advanced developers can create their own <code>Target</code>s
+ using a <code><a
+ href="/reference/android/support/v7/graphics/Target.Builder.html">Target.Builder</a></code>.</dd>
+
+</dl>
+
+<h2 id="extract-color-profiles">Extract color profiles</h2>
+
+<p>Based on the <a
+href="https://material.google.com/style/color.html#">standards
+of material design</a>, the palette library extracts commonly used color
+profiles from an image. Each profile is defined by a <code><a
+href="/reference/android/support/v7/graphics/Target.html">Target</a></code>,
+and colors extracted from the bitmap image are scored against each profile
+based on saturation, luminance, and population (number of pixels in the bitmap
+represented by the color). For each profile, the color with the best score
+defines that color profile for the given image.</p>
+
+<p>By default, a <code>Palette</code> object contains 16 primary colors from
+a given image. When generating your palette, you can <a
+href="#customize-your-palette">customize</a> its number of colors using the
+<code>Palette.Builder</code>. Extracting more colors provides more potential
+matches for each color profile but also causes <code>Palette.Builder</code> to
+take longer when generating the palette.</p>
+
+<p>The palette library attempts to extract the following six color
+profiles:</p>
+
+<ul>
+ <li>Light Vibrant</li>
+ <li>Vibrant</li>
+ <li>Dark Vibrant</li>
+ <li>Light Muted</li>
+ <li>Muted</li>
+ <li>Dark Muted</li>
+</ul>
+
+<p>Each of <code>Palette</code>’s <code>get<<em>Profile</em>>Color()</code>
+methods returns the color in the palette associated with that particular profile,
+where <code><<em>Profile</em>></code> is replaced by the name of one of the six
+color profiles. For example, the method to get the Dark Vibrant color profile is <code><a
+href="/reference/android/support/v7/graphics/Palette.html#getDarkVibrantColor(int)">getDarkVibrantColor()</a></code>.
+Since not all images will contain all color profiles, you must also provide
+a default color to return.</p>
+
+<p>Figure 1 displays a photo and its corresponding color
+profiles from the <code>get<<em>Profile</em>>Color()</code> methods.</p>
+
+<img src="/training/material/images/palette-library-color-profiles_2-1_2x.png" alt="" width="624"/>
+
+<p class="img-caption"><strong>Figure 1.</strong> An example image and its
+extracted color profiles given the default maximum color count (16) for the palette.</p>
+
+<h3 id="use-swatches">Use swatches to create color schemes</h3>
+
+<p>The <code>Palette</code> class also generates <code><a
+href="/reference/android/support/v7/graphics/Palette.Swatch.html">Palette.Swatch</a></code>
+objects for each color profile. <code>Palette.Swatch</code>
+objects contain the associated color for that profile, as well as the
+color’s population in pixels.</p>
+
+<p>Swatches have additional methods for accessing more information about the color
+profile, such as HSL values and pixel population. You can use swatches to help
+create more comprehensive color schemes and app themes using the <code><a
+href="/reference/android/support/v7/graphics/Palette.Swatch.html#getBodyTextColor()">getBodyTextColor()</a></code>
+and <code><a
+href="/reference/android/support/v7/graphics/Palette.Swatch.html#getTitleTextColor()">getTitleTextColor()</a></code>
+methods. These methods return colors appropriate for use over the swatch’s
+color.</p>
+
+<p>Each of <code>Palette</code>’s <code>get<<em>Profile</em>>Swatch()</code>
+methods returns the swatch associated with that particular profile,
+where <code><<em>Profile</em>></code> is replaced by the name of one of the six
+color profiles. Although the palette’s <code>get<<em>Profile</em>>Swatch()</code> methods
+do not require default value parameters, they return <code>null</code> if that
+particular profile does not exist in the image. Therefore, you should check that
+a swatch is not null before using it. For example, the following method
+returns the Vibrant swatch from a palette if the swatch is not null:</p>
+
+<pre class="prettyprint">
+// Return a palette's vibrant swatch after checking that it exists
+private Palette.Swatch checkVibrantSwatch(Palette p) {
+ Palette.Swatch vibrant = p.getVibrantSwatch();
+ if (vibrant != null) {
+ return vibrant;
+ }
+ // Throw error
+}
+</pre>
+
+<p>To access all colors in a palette, the <code><a
+href="/reference/android/support/v7/graphics/Palette.html#getSwatches()">getSwatches()</a></code>
+method returns a list of all swatches generated from an
+image, including the standard six color profiles.</p>
+
+<p>The following snippet of code uses the methods from the above code snippets to
+synchronously generate a palette, get its vibrant swatch, and change the colors of a
+toolbar to match the bitmap image. Figure 2 displays the resulting image and toolbar.</p>
+
+<div class="cols">
+
+ <div class="col-2of3">
+
+<pre class="prettyprint">
+// Set the background and text colors of a toolbar given a
+// bitmap image to match
+public void setToolbarColor(Bitmap bitmap) {
+ // Generate the palette and get the vibrant swatch
+ // See the createPaletteSync() and checkVibrantSwatch() methods
+ // from the code snippets above
+ Palette p = createPaletteSync(bitmap);
+ Palette.Swatch vibrantSwatch = checkVibrantSwatch(p);
+
+ // Set the toolbar background and text colors
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ toolbar.setBackgroundColor(vibrantSwatch.getRgb());
+ toolbar.setTitleTextColor(vibrantSwatch.getTitleTextColor());
+}
+</pre>
+
+ </div>
+
+ <div class="col-1of3">
+
+ <img src="/training/material/images/palette-library-title-text-color_2-1_2x.png" alt="" width="400"/>
+
+ <p class="img-caption"><strong>Figure 2.</strong> An example image with its
+ vibrant-colored toolbar and corresponding title text color.</p>
+
+ </div>
+
+</div>
+
+</body>
+</html>
diff --git a/docs/html/training/testing/unit-testing/instrumented-unit-tests.jd b/docs/html/training/testing/unit-testing/instrumented-unit-tests.jd
index 8fc4dca..df8b1bc 100644
--- a/docs/html/training/testing/unit-testing/instrumented-unit-tests.jd
+++ b/docs/html/training/testing/unit-testing/instrumented-unit-tests.jd
@@ -292,23 +292,21 @@
of any app failures.</p>
<p>
- Before you can start using Firebase Test Lab, you need to:
+ Before you can start using Firebase Test Lab, you need to do the following
+unless you already have a Google account and a Firebase project with the Blaze
+billing plan enabled:
</p>
<ol>
- <li>
- <a href="https://console.developers.google.com/freetrial">Create a
- Google Cloud Platform account</a> to use with active billing.
- </li>
-
- <li>
- <a href="https://support.google.com/cloud/answer/6251787">Create a Google
- Cloud project</a> for your app.
- </li>
-
- <li>
- <a href="https://support.google.com/cloud/answer/6288653">Set up an active
- billing account</a> and associate it with the project you just created.
+ <li><a href="https://accounts.google.com/">Create a Google account</a>,
+ if you don't have one already.</li>
+ <li>In the <a href="https://console.firebase.google.com/">Firebase
+ console</a>, click <b>Create New Project</b>.</li>
+ <li>In the Firebase console, click <b>Upgrade</b>, and then click <b>Select
+Plan</b> in the <b>Blaze</b> plan column.
+ <p class="note"><b>Note</b>: To learn about billing,
+see <a href="https://firebase.google.com/docs/test-lab/overview#billing">Test
+Lab billing</a>.</p>
</li>
</ol>
@@ -318,10 +316,10 @@
</h4>
<p>
- Android Studio provides integrated tools that allow you to configure how you
- want to deploy your tests to Firebase Test Lab. After you have created a Google
- Cloud project with active billing, you can create a test configuration and
- run your tests:
+Android Studio provides integrated tools that allow you to configure how you
+want to deploy your tests to Firebase Test Lab. After you have created a
+Firebase project with Blaze plan billing, you can create a test configuration
+and run your tests:
</p>
<ol>
@@ -329,7 +327,8 @@
the main menu.
</li>
- <li>Click <strong>Add New Configuration (+)</strong> and select
+ <li>Click <strong>Add New Configuration</strong> <img
+src="/studio/images/buttons/ic_plus.png" alt="" class="inline-icon"/> and select
<strong>Android Tests</strong>.
</li>
@@ -340,7 +339,7 @@
</li>
<li>From the <em>Target</em> drop-down menu under <em>Deployment Target
- Options</em>, select <strong>Cloud Test Lab Device Matrix</strong>.
+ Options</em>, select <strong>Firebase Test Lab Device Matrix</strong>.
</li>
<li>If you are not logged in, click <strong>Connect to Google Cloud
@@ -348,9 +347,9 @@
</li>
<li>Next to <em>Cloud Project</em>, click the <img src=
- "{@docRoot}images/tools/as-wrench.png" alt="wrench and nut" style=
- "vertical-align:bottom;margin:0;"> button and select your Google Cloud
- Platform project from the list.
+ "{@docRoot}images/tools/as-wrench.png" alt="" class="inline-icon"/>
+ button and select your Firebase
+ project from the list.
</li>
</ol>
</li>
@@ -359,7 +358,7 @@
<ol type="a">
<li>Next to the <em>Matrix Configuration</em> drop-down list, click <strong>
Open Dialog</strong> <img src="{@docRoot}images/tools/as-launchavdm.png"
- alt="ellipses button" style="vertical-align:bottom;margin:0;">.
+ alt="" class="inline-icon">.
</li>
<li>Click <strong>Add New Configuration (+)</strong>.
@@ -385,8 +384,7 @@
</li>
<li>Run your tests by clicking <strong>Run</strong> <img src=
- "{@docRoot}images/tools/as-run.png" alt="" style=
- "vertical-align:bottom;margin:0;">.
+ "{@docRoot}images/tools/as-run.png" alt="" class="inline-icon"/>.
</li>
</ol>
@@ -404,7 +402,7 @@
When Firebase Test Lab completes running your tests, the <em>Run</em> window
will open to show the results, as shown in figure 2. You may need to click
<strong>Show Passed</strong> <img src="{@docRoot}images/tools/as-ok.png" alt=
- "" style="vertical-align:bottom;margin:0;"> to see all your executed tests.
+ "" class="inline-icon"/> to see all your executed tests.
</p>
<img src="{@docRoot}images/training/ctl-test-results.png" alt="">
@@ -416,15 +414,7 @@
<p>
You can also analyze your tests on the web by following the link displayed at
- the beginning of the test execution log in the <em>Run</em> window, as shown
- in figure 3.
-</p>
-
-<img src="{@docRoot}images/training/ctl-exec-log.png" alt="">
-
-<p class="img-caption">
- <strong>Figure 3.</strong> Click the link to view detailed test results on
- the web.
+ the beginning of the test execution log in the <em>Run</em> window.
</p>
<p>
diff --git a/docs/html/training/tv/playback/index.jd b/docs/html/training/tv/playback/index.jd
index d5e4e67..34c6287 100644
--- a/docs/html/training/tv/playback/index.jd
+++ b/docs/html/training/tv/playback/index.jd
@@ -69,6 +69,10 @@
<dd>Learn how to use the Leanback support library to guide a user through a series of
decisions.</dd>
+ <dt><b><a href="onboarding.html">Introducing First-time Users to Your App</a></b></dt>
+ <dd>Learn how to use the Leanback support library to show first-time users
+ how to get the most out of your app.</dd>
+
<dt><b><a href="options.html">Enabling Background Playback</a></b></dt>
<dd>Learn how to continue playback when the user clicks on <strong>Home</strong>.</dd>
</dl>
diff --git a/docs/html/training/tv/playback/onboarding.jd b/docs/html/training/tv/playback/onboarding.jd
new file mode 100644
index 0000000..bb41bec
--- /dev/null
+++ b/docs/html/training/tv/playback/onboarding.jd
@@ -0,0 +1,377 @@
+page.title=Introducing First-time Users to Your App
+page.tags=tv,onboarding,OnboardingFragment
+page.keywords=tv,onboarding,OnboardingFragment
+helpoutsWidget=true
+
+trainingnavtop=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+ <h2>This lesson teaches you to</h2>
+ <ol>
+ <li><a href="#addFragment">Add an OnboardingFragment</a></li>
+ <li><a href="#pageContent">Add OnboardingFragment Pages</a></li>
+ <li><a href="#logoScreen">Add an Initial Logo Screen</a></li>
+ <li><a href="#pageAnimations">Customize Page Animations</a></li>
+ <li><a href="#themes">Customize Themes</a></li>
+ </ol>
+ <h2>Try it out</h2>
+ <ul>
+ <li><a class="external-link" href="https://github.com/googlesamples/androidtv-Leanback">Android
+ Leanback sample app</a></li>
+ </ul>
+</div>
+</div>
+
+<p>
+To show a first-time user how to get the most from your app, present
+onboarding information at app startup. Here are some examples of onboarding
+information:
+</p>
+
+<ul>
+<li>Present detailed information on which channels are available when a user
+first accesses a channel app.</li>
+<li>Call attention to noteworthy features in your app.</li>
+<li>Illustrate any required or recommended steps that users should take when
+using the app for the first time.</li>
+</ul>
+
+<p>The <a href=
+"{@docRoot}tools/support-library/features.html#v17-leanback">v17 Leanback
+support library</a> provides the
+{@link android.support.v17.leanback.app.OnboardingFragment} class for
+presenting first-time user information. This lesson describes how to use the
+{@link android.support.v17.leanback.app.OnboardingFragment} class to present
+introductory information that is shown when the app launches for the first
+time. {@link android.support.v17.leanback.app.OnboardingFragment} uses TV UI
+best practices to present the information in a way that matches TV UI styles,
+and is easy to navigate on TV devices.</p>
+
+<img src="{@docRoot}images/training/tv/playback/onboarding-fragment.png"
+srcset="{@docRoot}images/training/tv/playback/onboarding-fragment.png 1x,
+{@docRoot}images/training/tv/playback/onboarding-fragment_2x.png 2x" />
+<p class="img-caption"><strong>Figure 1.</strong> An example
+OnboardingFragment.</p>
+
+<p>Your {@link android.support.v17.leanback.app.OnboardingFragment} should
+not contain UI elements that require user input, such as buttons and fields.
+Similarly, it should not be used as a UI element for a task the user will do
+regularly. If you need to present a multi-page UI that requires
+user input, consider using a
+{@link android.support.v17.leanback.app.GuidedStepFragment}.</p>
+
+<h2 id="addFragment">Add an OnboardingFragment</h2>
+
+<p>To add an {@link android.support.v17.leanback.app.OnboardingFragment}
+to your app, implement a class that extends
+the {@link android.support.v17.leanback.app.OnboardingFragment} class. Add
+this fragment to an activity, either via the activity's layout XML, or
+programmatically. Make sure the activity or
+fragment is using a theme derived from
+{@link android.support.v17.leanback.R.style#Theme_Leanback_Onboarding},
+as described in <a href="#themes">Customize Themes</a>.</p>
+
+<p>In the {@link android.app.Activity#onCreate onCreate()} method of your
+app's main activity, call
+{@link android.app.Activity#startActivity startActivity()}
+with an {@link android.content.Intent} that points to your
+{@link android.support.v17.leanback.app.OnboardingFragment OnboardingFragment's}
+parent activity. This ensures that your
+{@link android.support.v17.leanback.app.OnboardingFragment} appears as
+soon as your app starts.<p>
+
+<p>To ensure that the
+{@link android.support.v17.leanback.app.OnboardingFragment} only appears the
+first time that the user starts your app, use a
+{@link android.content.SharedPreferences} object
+to track whether the user has already viewed the
+{@link android.support.v17.leanback.app.OnboardingFragment}. Define a boolean
+value that changes to true when the user finishes viewing the
+{@link android.support.v17.leanback.app.OnboardingFragment}. Check
+this value in your main activity’s
+{@link android.app.Activity#onCreate onCreate()}, and only start the
+{@link android.support.v17.leanback.app.OnboardingFragment} parent activity if
+the value is false. The following example shows an override of
+{@link android.app.Activity#onCreate onCreate()} that checks for a
+{@link android.content.SharedPreferences} value and, if not set to true, calls
+{@link android.app.Activity#startActivity startActivity()} to
+show the {@link android.support.v17.leanback.app.OnboardingFragment}:</p>
+
+<pre>
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ SharedPreferences sharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(this);
+ // Check if we need to display our OnboardingFragment
+ if (!sharedPreferences.getBoolean(
+ MyOnboardingFragment.COMPLETED_ONBOARDING_PREF_NAME, false)) {
+ // The user hasn't seen the OnboardingFragment yet, so show it
+ startActivity(new Intent(this, OnboardingActivity.class));
+ }
+}
+</pre>
+
+<p>After the user views the
+{@link android.support.v17.leanback.app.OnboardingFragment}, mark it as viewed
+using the {@link android.content.SharedPreferences} object. To do this, in your
+{@link android.support.v17.leanback.app.OnboardingFragment}, override
+{@link android.support.v17.leanback.app.OnboardingFragment#onFinishFragment
+onFinishFragment()} and set your {@link android.content.SharedPreferences} value
+to true, as shown in the following example:
+
+<pre>
+@Override
+protected void onFinishFragment() {
+ super.onFinishFragment();
+ // User has seen OnboardingFragment, so mark our SharedPreferences
+ // flag as completed so that we don't show our OnboardingFragment
+ // the next time the user launches the app.
+ SharedPreferences.Editor sharedPreferencesEditor =
+ PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
+ sharedPreferencesEditor.putBoolean(
+ COMPLETED_ONBOARDING_PREF_NAME, true);
+ sharedPreferencesEditor.apply();
+}
+</pre>
+
+<h2 id="pageContent">Add OnboardingFragment Pages</h2>
+
+<p>After you add your
+{@link android.support.v17.leanback.app.OnboardingFragment}, you need to define
+the onboarding pages. An
+{@link android.support.v17.leanback.app.OnboardingFragment} displays content
+in a series of ordered pages. Each page can have a title, description, and
+several sub-views that can contain images or animations.</p>
+
+<img src="{@docRoot}images/training/tv/playback/onboarding-fragment-diagram.png"
+/><p class="img-caption"><strong>Figure 2.</strong> OnboardingFragment
+page elements.
+</p>
+
+<p>Figure 2 shows an example page with callouts marking customizable page
+elements that your {@link android.support.v17.leanback.app.OnboardingFragment}
+can provide. The page elements are:</p>
+
+<ol>
+<li>The page title.</li>
+<li>The page description.</li>
+<li>The page content view, in this case a simple green checkmark in a grey box.
+This view is optional. Use this view to illustrate page details such as a
+screenshot that highlights the app feature that the page describes.</li>
+<li>The page background view, in this case a simple blue gradient. This view
+always renders behind other views on the page. This view is optional.</li>
+<li>The page foreground view, in this case a logo. This view always renders
+in front of all other views on the page. This view is optional.</li>
+</ol>
+
+<p>Initialize page information when your
+{@link android.support.v17.leanback.app.OnboardingFragment} is first created
+or attached to the parent activity, as the system requests page
+information when it creates the fragment's view. You can initialize page
+information in your class constructor or in an override of
+{@link android.app.Fragment#onAttach onAttach()}.</p>
+
+<p>Override each of the following methods that provide page information
+to the system:</p>
+
+<ul>
+<li>{@link android.support.v17.leanback.app.OnboardingFragment#getPageCount
+getPageCount()} returns the number of pages in your
+{@link android.support.v17.leanback.app.OnboardingFragment}.</li>
+<li>{@link android.support.v17.leanback.app.OnboardingFragment#getPageTitle
+getPageTitle()} returns the title for the requested page number.</li>
+<li>{@link android.support.v17.leanback.app.OnboardingFragment#getPageDescription
+getPagedescription()} returns the description for the requested page
+number.</li>
+</ul>
+
+<p>Override each of the following methods to provide optional sub-views used
+to display images or animations:</p>
+
+<ul>
+<li>{@link android.support.v17.leanback.app.OnboardingFragment#onCreateBackgroundView
+onCreateBackgroundView()} returns a {@link android.view.View} that you
+create to act as the background view, or null if no background view is needed.
+<li>{@link android.support.v17.leanback.app.OnboardingFragment#onCreateContentView
+onCreateContentView()} returns a {@link android.view.View} that you
+create to act as the content view, or null if no content view is needed.
+<li>{@link android.support.v17.leanback.app.OnboardingFragment#onCreateForegroundView
+onCreateForegroundView()} returns a {@link android.view.View} that you
+create to act as the foreground view, or null if no foreground view is needed.
+</ul>
+
+<p>The system adds the {@link android.view.View} that you create to the page
+layout. The following example overrides
+{@link android.support.v17.leanback.app.OnboardingFragment#onCreateContentView
+onCreateContentView()} and returns an {@link android.widget.ImageView}:</p>
+
+<pre>
+private ImageView mContentView;
+...
+@Override
+protected View onCreateContentView(LayoutInflater inflater, ViewGroup container) {
+ mContentView = new ImageView(getContext());
+ mContentView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ mContentView.setImageResource(R.drawable.onboarding_content_view);
+ mContentView.setPadding(0, 32, 0, 32);
+ return mContentView;
+}
+</pre>
+
+<h2 id="logoScreen">Add an Initial Logo Screen</h2>
+
+<p>Your {@link android.support.v17.leanback.app.OnboardingFragment} can start
+with an optional logo screen that introduces your app. If you want to display
+a {@link android.graphics.drawable.Drawable} as your logo screen, in your
+{@link android.support.v17.leanback.app.OnboardingFragment OnboardingFragment's}
+{@link android.app.Fragment#onCreate onCreate()} method, call
+{@link android.support.v17.leanback.app.OnboardingFragment#setLogoResourceId
+setLogoResourceId()} with the ID of your
+{@link android.graphics.drawable.Drawable}. The
+system will fade in and briefly display this
+{@link android.graphics.drawable.Drawable}, and then fade out the
+{@link android.graphics.drawable.Drawable}
+before displaying the first page of your
+{@link android.support.v17.leanback.app.OnboardingFragment}.</p>
+
+<p>If you want to provide a custom animation for your logo screen, instead of
+calling
+{@link android.support.v17.leanback.app.OnboardingFragment#setLogoResourceId
+setLogoResourceId()}, override
+{@link android.support.v17.leanback.app.OnboardingFragment#onCreateLogoAnimation
+onCreateLogoAnimation()} and return an {@link android.animation.Animator}
+object that renders your custom animation, as shown in the following example:
+</p>
+
+<pre>
+@Override
+public Animator onCreateLogoAnimation() {
+ return AnimatorInflater.loadAnimator(mContext,
+ R.animator.onboarding_logo_screen_animation);
+}
+</pre>
+
+<h2 id="pageAnimations">Customize Page Animations</h2>
+
+<p>The system uses default animations when displaying the first page of your
+{@link android.support.v17.leanback.app.OnboardingFragment} and when the user
+navigates to a different page. You can customize these animations by
+overriding methods in your
+{@link android.support.v17.leanback.app.OnboardingFragment}.</p>
+
+<p>To customize the animation that appears on your first page,
+override
+{@link android.support.v17.leanback.app.OnboardingFragment#onCreateEnterAnimation
+onCreateEnterAnimation()} and return an {@link android.animation.Animator}.
+The following example creates an
+{@link android.animation.Animator} that scales the content view
+horizontally:</p>
+
+<pre>
+@Override
+protected Animator onCreateEnterAnimation() {
+ Animator startAnimator = ObjectAnimator.ofFloat(mContentView,
+ View.SCALE_X, 0.2f, 1.0f).setDuration(ANIMATION_DURATION);
+ return startAnimator;
+}
+</pre>
+
+<p>To customize the animation used when the user navigates to a different page,
+override
+{@link android.support.v17.leanback.app.OnboardingFragment#onPageChanged
+onPageChanged()}. In your
+{@link android.support.v17.leanback.app.OnboardingFragment#onPageChanged
+onPageChanged()} method, create {@link android.animation.Animator Animators}
+that remove the previous page and display the next page, add these to an
+{@link android.animation.AnimatorSet}, and play the set. The following
+example uses a fade-out animation to remove the previous page, updates the
+content view image, and uses a fade-in animation to display the next page:</p>
+
+<pre>
+@Override
+protected void onPageChanged(final int newPage, int previousPage) {
+ // Create a fade-out animation used to fade out previousPage and, once
+ // done, swaps the contentView image with the next page's image.
+ Animator fadeOut = ObjectAnimator.ofFloat(mContentView,
+ View.ALPHA, 1.0f, 0.0f).setDuration(ANIMATION_DURATION);
+ fadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mContentView.setImageResource(pageImages[newPage]);
+ }
+ });
+ // Create a fade-in animation used to fade in nextPage
+ Animator fadeIn = ObjectAnimator.ofFloat(mContentView,
+ View.ALPHA, 0.0f, 1.0f).setDuration(ANIMATION_DURATION);
+ // Create AnimatorSet with our fade-out and fade-in animators, and start it
+ AnimatorSet set = new AnimatorSet();
+ set.playSequentially(fadeOut, fadeIn);
+ set.start();
+}
+</pre>
+
+<p>For more details about how to create
+{@link android.animation.Animator Animators} and
+{@link android.animation.AnimatorSet AnimatorSets}, see
+<a href="https://developer.android.com/guide/topics/graphics/prop-animation.html">
+Property Animations</a>.</p>
+
+<h2 id="themes">Customize Themes</h2>
+
+<p>Any {@link android.support.v17.leanback.app.OnboardingFragment}
+implementation must use either the
+{@link android.support.v17.leanback.R.style#Theme_Leanback_Onboarding} theme
+or a theme that inherits from
+{@link android.support.v17.leanback.R.style#Theme_Leanback_Onboarding}. Set the
+theme for your {@link android.support.v17.leanback.app.OnboardingFragment} by
+doing one of the following:</p>
+
+<ul>
+<li>Set the {@link android.support.v17.leanback.app.OnboardingFragment
+OnboardingFragment's} parent activity to use the desired theme. The following
+example shows how to set an activity to use
+{@link android.support.v17.leanback.R.style#Theme_Leanback_Onboarding} in the
+app manifest:
+<pre>
+<activity
+ android:name=".OnboardingActivity"
+ android:enabled="true"
+ android:exported="true"
+ android:theme="@style/Theme.Leanback.Onboarding">
+</activity>
+</pre>
+</li>
+<li>
+Set the theme in the parent activity by using the
+{@link android.support.v17.leanback.R.styleable#LeanbackOnboardingTheme_onboardingTheme}
+attribute in a custom activity theme. Point this attribute to another
+custom theme that only the
+{@link android.support.v17.leanback.app.OnboardingFragment}
+objects in your activity use. Use this approach if your activity already uses
+a custom theme and you don't want to apply
+{@link android.support.v17.leanback.app.OnboardingFragment} styles to other
+views in the activity.
+</li>
+<li>Override
+{@link android.support.v17.leanback.app.OnboardingFragment#onProvideTheme
+onProvideTheme()} and return the desired theme. Use this approach if
+multiple activities use your
+{@link android.support.v17.leanback.app.OnboardingFragment}
+or if the parent activity can't use the desired theme.
+The following example overrides
+{@link android.support.v17.leanback.app.OnboardingFragment#onProvideTheme
+onProvideTheme()} and returns
+{@link android.support.v17.leanback.R.style#Theme_Leanback_Onboarding}:
+<pre>
+@Override
+public int onProvideTheme() {
+ return R.style.Theme_Leanback_Onboarding;
+}
+</pre>
+</li>
+</ul>
\ No newline at end of file
diff --git a/docs/html/training/tv/start/hardware.jd b/docs/html/training/tv/start/hardware.jd
index 97cf7ff..0639871 100644
--- a/docs/html/training/tv/start/hardware.jd
+++ b/docs/html/training/tv/start/hardware.jd
@@ -227,13 +227,19 @@
</tr>
<tr>
<td>{@link android.Manifest.permission#ACCESS_COARSE_LOCATION}</td>
- <td>{@code android.hardware.location} <em>and</em> <br>
- {@code android.hardware.location.network}</td>
+ <td>
+ <p>{@code android.hardware.location}</p>
+ <p>{@code android.hardware.location.network} (Target API level 20 or lower
+ only.)</p>
+ </td>
</tr>
<tr>
<td>{@link android.Manifest.permission#ACCESS_FINE_LOCATION}</td>
- <td>{@code android.hardware.location} <em>and</em> <br>
- {@code android.hardware.location.gps}</td>
+ <td>
+ <p>{@code android.hardware.location}</p>
+ <p>{@code android.hardware.location.gps} (Target API level 20 or lower
+ only.)</p>
+ </td>
</tr>
</table>
@@ -246,6 +252,13 @@
required ({@code android:required="false"}).
</p>
+<p class="note">
+ <strong>Note:</strong> If your app targets Android 5.0 (API level 21) or
+ higher and uses the <code>ACCESS_COARSE_LOCATION</code> or
+ <code>ACCESS_FINE_LOCATION</code> permission, users can still install your
+ app on a TV device, even if the TV device doesn't have a network card or a GPS
+ receiver.
+</p>
<h3 id="check-features">Checking for hardware features</h2>
diff --git a/docs/image_sources/training/tv/playback/onboarding-fragment-diagram.graffle.zip b/docs/image_sources/training/tv/playback/onboarding-fragment-diagram.graffle.zip
new file mode 100644
index 0000000..89a799b
--- /dev/null
+++ b/docs/image_sources/training/tv/playback/onboarding-fragment-diagram.graffle.zip
Binary files differ
diff --git a/drm/jni/Android.mk b/drm/jni/Android.mk
index 08c7b95..d0797a9 100644
--- a/drm/jni/Android.mk
+++ b/drm/jni/Android.mk
@@ -37,7 +37,6 @@
$(TOP)/frameworks/av/drm/libdrmframework/include \
$(TOP)/frameworks/av/drm/libdrmframework/plugins/common/include \
$(TOP)/frameworks/av/include \
- $(TOP)/libcore/include
LOCAL_MODULE_TAGS := optional
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 3cbc4f0..81bbfa9 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2397,13 +2397,12 @@
* Note: just like Canvas.drawText, this will respect the Align setting in
* the paint.
*
- * @param text The text to retrieve the path from
- * @param index The index of the first character in text
- * @param count The number of characterss starting with index
- * @param x The x coordinate of the text's origin
- * @param y The y coordinate of the text's origin
- * @param path The path to receive the data describing the text. Must
- * be allocated by the caller.
+ * @param text the text to retrieve the path from
+ * @param index the index of the first character in text
+ * @param count the number of characters starting with index
+ * @param x the x coordinate of the text's origin
+ * @param y the y coordinate of the text's origin
+ * @param path the path to receive the data describing the text. Must be allocated by the caller
*/
public void getTextPath(char[] text, int index, int count,
float x, float y, Path path) {
@@ -2419,13 +2418,12 @@
* Note: just like Canvas.drawText, this will respect the Align setting
* in the paint.
*
- * @param text The text to retrieve the path from
- * @param start The first character in the text
- * @param end 1 past the last charcter in the text
- * @param x The x coordinate of the text's origin
- * @param y The y coordinate of the text's origin
- * @param path The path to receive the data describing the text. Must
- * be allocated by the caller.
+ * @param text the text to retrieve the path from
+ * @param start the first character in the text
+ * @param end 1 past the last character in the text
+ * @param x the x coordinate of the text's origin
+ * @param y the y coordinate of the text's origin
+ * @param path the path to receive the data describing the text. Must be allocated by the caller
*/
public void getTextPath(String text, int start, int end,
float x, float y, Path path) {
@@ -2440,11 +2438,10 @@
* Return in bounds (allocated by the caller) the smallest rectangle that
* encloses all of the characters, with an implied origin at (0,0).
*
- * @param text String to measure and return its bounds
- * @param start Index of the first char in the string to measure
- * @param end 1 past the last char in the string measure
- * @param bounds Returns the unioned bounds of all the text. Must be
- * allocated by the caller.
+ * @param text string to measure and return its bounds
+ * @param start index of the first char in the string to measure
+ * @param end 1 past the last char in the string to measure
+ * @param bounds returns the unioned bounds of all the text. Must be allocated by the caller
*/
public void getTextBounds(String text, int start, int end, Rect bounds) {
if ((start | end | (end - start) | (text.length() - end)) < 0) {
@@ -2460,11 +2457,33 @@
* Return in bounds (allocated by the caller) the smallest rectangle that
* encloses all of the characters, with an implied origin at (0,0).
*
- * @param text Array of chars to measure and return their unioned bounds
- * @param index Index of the first char in the array to measure
- * @param count The number of chars, beginning at index, to measure
- * @param bounds Returns the unioned bounds of all the text. Must be
- * allocated by the caller.
+ * @param text text to measure and return its bounds
+ * @param start index of the first char in the text to measure
+ * @param end 1 past the last char in the text to measure
+ * @param bounds returns the unioned bounds of all the text. Must be allocated by the caller
+ * @hide
+ */
+ public void getTextBounds(CharSequence text, int start, int end, Rect bounds) {
+ if ((start | end | (end - start) | (text.length() - end)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (bounds == null) {
+ throw new NullPointerException("need bounds Rect");
+ }
+ char[] buf = TemporaryBuffer.obtain(end - start);
+ TextUtils.getChars(text, start, end, buf, 0);
+ getTextBounds(buf, 0, end - start, bounds);
+ TemporaryBuffer.recycle(buf);
+ }
+
+ /**
+ * Return in bounds (allocated by the caller) the smallest rectangle that
+ * encloses all of the characters, with an implied origin at (0,0).
+ *
+ * @param text array of chars to measure and return their unioned bounds
+ * @param index index of the first char in the array to measure
+ * @param count the number of chars, beginning at index, to measure
+ * @param bounds returns the unioned bounds of all the text. Must be allocated by the caller
*/
public void getTextBounds(char[] text, int index, int count, Rect bounds) {
if ((index | count) < 0 || index + count > text.length) {
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 29bf963..a14609f 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -18,8 +18,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.os.Handler;
+import android.view.ViewTreeObserver.OnDrawListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -105,6 +108,32 @@
}
/**
+ * Requests for the display content of a {@link SurfaceView} to be copied
+ * into a provided {@link Bitmap}.
+ *
+ * The contents of the source will be scaled to fit exactly inside the bitmap.
+ * The pixel format of the source buffer will be converted, as part of the copy,
+ * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * in the SurfaceView's Surface will be used as the source of the copy.
+ *
+ * @param source The source from which to copy
+ * @param srcRect The area of the source to copy from. If this is null
+ * the copy area will be the entire surface. The rect will be clamped to
+ * the bounds of the Surface.
+ * @param dest The destination of the copy. The source will be scaled to
+ * match the width, height, and format of this bitmap.
+ * @param listener Callback for when the pixel copy request completes
+ * @param listenerThread The callback will be invoked on this Handler when
+ * the copy is finished.
+ */
+ public static void request(@NonNull SurfaceView source, @Nullable Rect srcRect,
+ @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
+ @NonNull Handler listenerThread) {
+ request(source.getHolder().getSurface(), srcRect,
+ dest, listener, listenerThread);
+ }
+
+ /**
* Requests a copy of the pixels from a {@link Surface} to be copied into
* a provided {@link Bitmap}.
*
@@ -122,12 +151,40 @@
*/
public static void request(@NonNull Surface source, @NonNull Bitmap dest,
@NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
+ request(source, null, dest, listener, listenerThread);
+ }
+
+ /**
+ * Requests a copy of the pixels at the provided {@link Rect} from
+ * a {@link Surface} to be copied into a provided {@link Bitmap}.
+ *
+ * The contents of the source rect will be scaled to fit exactly inside the bitmap.
+ * The pixel format of the source buffer will be converted, as part of the copy,
+ * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * in the Surface will be used as the source of the copy.
+ *
+ * @param source The source from which to copy
+ * @param srcRect The area of the source to copy from. If this is null
+ * the copy area will be the entire surface. The rect will be clamped to
+ * the bounds of the Surface.
+ * @param dest The destination of the copy. The source will be scaled to
+ * match the width, height, and format of this bitmap.
+ * @param listener Callback for when the pixel copy request completes
+ * @param listenerThread The callback will be invoked on this Handler when
+ * the copy is finished.
+ */
+ public static void request(@NonNull Surface source, @Nullable Rect srcRect,
+ @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
+ @NonNull Handler listenerThread) {
validateBitmapDest(dest);
if (!source.isValid()) {
throw new IllegalArgumentException("Surface isn't valid, source.isValid() == false");
}
+ if (srcRect != null && srcRect.isEmpty()) {
+ throw new IllegalArgumentException("sourceRect is empty");
+ }
// TODO: Make this actually async and fast and cool and stuff
- int result = ThreadedRenderer.copySurfaceInto(source, dest);
+ int result = ThreadedRenderer.copySurfaceInto(source, srcRect, dest);
listenerThread.post(new Runnable() {
@Override
public void run() {
@@ -136,6 +193,86 @@
});
}
+ /**
+ * Requests a copy of the pixels from a {@link Window} to be copied into
+ * a provided {@link Bitmap}.
+ *
+ * The contents of the source will be scaled to fit exactly inside the bitmap.
+ * The pixel format of the source buffer will be converted, as part of the copy,
+ * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * in the Window's Surface will be used as the source of the copy.
+ *
+ * Note: This is limited to being able to copy from Window's with a non-null
+ * DecorView. If {@link Window#peekDecorView()} is null this throws an
+ * {@link IllegalArgumentException}. It will similarly throw an exception
+ * if the DecorView has not yet acquired a backing surface. It is recommended
+ * that {@link OnDrawListener} is used to ensure that at least one draw
+ * has happened before trying to copy from the window, otherwise either
+ * an {@link IllegalArgumentException} will be thrown or an error will
+ * be returned to the {@link OnPixelCopyFinishedListener}.
+ *
+ * @param source The source from which to copy
+ * @param dest The destination of the copy. The source will be scaled to
+ * match the width, height, and format of this bitmap.
+ * @param listener Callback for when the pixel copy request completes
+ * @param listenerThread The callback will be invoked on this Handler when
+ * the copy is finished.
+ */
+ public static void request(@NonNull Window source, @NonNull Bitmap dest,
+ @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
+ request(source, null, dest, listener, listenerThread);
+ }
+
+ /**
+ * Requests a copy of the pixels at the provided {@link Rect} from
+ * a {@link Window} to be copied into a provided {@link Bitmap}.
+ *
+ * The contents of the source rect will be scaled to fit exactly inside the bitmap.
+ * The pixel format of the source buffer will be converted, as part of the copy,
+ * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+ * in the Window's Surface will be used as the source of the copy.
+ *
+ * Note: This is limited to being able to copy from Window's with a non-null
+ * DecorView. If {@link Window#peekDecorView()} is null this throws an
+ * {@link IllegalArgumentException}. It will similarly throw an exception
+ * if the DecorView has not yet acquired a backing surface. It is recommended
+ * that {@link OnDrawListener} is used to ensure that at least one draw
+ * has happened before trying to copy from the window, otherwise either
+ * an {@link IllegalArgumentException} will be thrown or an error will
+ * be returned to the {@link OnPixelCopyFinishedListener}.
+ *
+ * @param source The source from which to copy
+ * @param srcRect The area of the source to copy from. If this is null
+ * the copy area will be the entire surface. The rect will be clamped to
+ * the bounds of the Surface.
+ * @param dest The destination of the copy. The source will be scaled to
+ * match the width, height, and format of this bitmap.
+ * @param listener Callback for when the pixel copy request completes
+ * @param listenerThread The callback will be invoked on this Handler when
+ * the copy is finished.
+ */
+ public static void request(@NonNull Window source, @Nullable Rect srcRect,
+ @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
+ @NonNull Handler listenerThread) {
+ validateBitmapDest(dest);
+ if (source == null) {
+ throw new IllegalArgumentException("source is null");
+ }
+ if (source.peekDecorView() == null) {
+ throw new IllegalArgumentException(
+ "Only able to copy windows with decor views");
+ }
+ Surface surface = null;
+ if (source.peekDecorView().getViewRootImpl() != null) {
+ surface = source.peekDecorView().getViewRootImpl().mSurface;
+ }
+ if (surface == null || !surface.isValid()) {
+ throw new IllegalArgumentException(
+ "Window doesn't have a backing surface!");
+ }
+ request(surface, srcRect, dest, listener, listenerThread);
+ }
+
private static void validateBitmapDest(Bitmap bitmap) {
// TODO: Pre-check max texture dimens if we can
if (bitmap == null) {
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 513e376..81a1831 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -2,6 +2,8 @@
include $(CLEAR_VARS)
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+BUGREPORT_FONT_CACHE_USAGE := true
+
# Enables fine-grained GLES error checking
# If set to true, every GLES call is wrapped & error checked
# Has moderate overhead
@@ -135,6 +137,13 @@
# clang's warning is broken, see: https://llvm.org/bugs/show_bug.cgi?id=21629
hwui_cflags += -Wno-missing-braces
+ifeq (true, $(BUGREPORT_FONT_CACHE_USAGE))
+ hwui_src_files += \
+ font/FontCacheHistoryTracker.cpp
+ hwui_cflags += -DBUGREPORT_FONT_CACHE_USAGE
+endif
+
+
ifndef HWUI_COMPILE_SYMBOLS
hwui_cflags += -fvisibility=hidden
endif
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index fe3d859..741cdcc 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -21,6 +21,9 @@
#include "Properties.h"
#include "renderstate/RenderState.h"
#include "ShadowTessellator.h"
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+#include "font/FontCacheHistoryTracker.h"
+#endif
#include "utils/GLUtils.h"
#include <cutils/properties.h>
@@ -191,12 +194,7 @@
log.appendFormat(" PatchCache %8d / %8d\n",
patchCache.getSize(), patchCache.getMaxSize());
- const uint32_t sizeA8 = fontRenderer.getFontRendererSize(GL_ALPHA);
- const uint32_t sizeRGBA = fontRenderer.getFontRendererSize(GL_RGBA);
- log.appendFormat(" FontRenderer A8 %8d / %8d\n", sizeA8, sizeA8);
- log.appendFormat(" FontRenderer RGBA %8d / %8d\n", sizeRGBA, sizeRGBA);
- log.appendFormat(" FontRenderer total %8d / %8d\n", sizeA8 + sizeRGBA,
- sizeA8 + sizeRGBA);
+ fontRenderer.dumpMemoryUsage(log);
log.appendFormat("Other:\n");
log.appendFormat(" FboCache %8d / %8d\n",
@@ -209,11 +207,14 @@
total += tessellationCache.getSize();
total += dropShadowCache.getSize();
total += patchCache.getSize();
- total += fontRenderer.getFontRendererSize(GL_ALPHA);
- total += fontRenderer.getFontRendererSize(GL_RGBA);
+ total += fontRenderer.getSize();
log.appendFormat("Total memory usage:\n");
log.appendFormat(" %d bytes, %.2f MB\n", total, total / 1024.0f / 1024.0f);
+
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+ fontRenderer.getFontRenderer().historyTracker().dump(log);
+#endif
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 25dc92c..9b60dfc 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -151,10 +151,17 @@
for (uint32_t i = 0; i < mACacheTextures.size(); i++) {
mACacheTextures[i]->init();
+
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+ mHistoryTracker.glyphsCleared(mACacheTextures[i]);
+#endif
}
for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) {
mRGBACacheTextures[i]->init();
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+ mHistoryTracker.glyphsCleared(mRGBACacheTextures[i]);
+#endif
}
mDrawn = false;
@@ -166,6 +173,9 @@
CacheTexture* cacheTexture = cacheTextures[i];
if (cacheTexture->getPixelBuffer()) {
cacheTexture->init();
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+ mHistoryTracker.glyphsCleared(cacheTexture);
+#endif
LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
while (it.next()) {
it.value()->invalidateTextureCache(cacheTexture);
@@ -368,6 +378,10 @@
}
cachedGlyph->mIsValid = true;
+
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+ mHistoryTracker.glyphUploaded(cacheTexture, startX, startY, glyph.fWidth, glyph.fHeight);
+#endif
}
CacheTexture* FontRenderer::createCacheTexture(int width, int height, GLenum format,
@@ -730,18 +744,67 @@
return size;
}
-uint32_t FontRenderer::getCacheSize(GLenum format) const {
- switch (format) {
- case GL_ALPHA: {
- return calculateCacheSize(mACacheTextures);
- }
- case GL_RGBA: {
- return calculateCacheSize(mRGBACacheTextures);
- }
- default: {
- return 0;
+static uint32_t calculateFreeCacheSize(const std::vector<CacheTexture*>& cacheTextures) {
+ uint32_t size = 0;
+ for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+ CacheTexture* cacheTexture = cacheTextures[i];
+ if (cacheTexture && cacheTexture->getPixelBuffer()) {
+ size += cacheTexture->calculateFreeMemory();
}
}
+ return size;
+}
+
+const std::vector<CacheTexture*>& FontRenderer::cacheTexturesForFormat(GLenum format) const {
+ switch (format) {
+ case GL_ALPHA: {
+ return mACacheTextures;
+ }
+ case GL_RGBA: {
+ return mRGBACacheTextures;
+ }
+ default: {
+ LOG_ALWAYS_FATAL("Unsupported format: %d", format);
+ // Impossible to hit this, but the compiler doesn't know that
+ return *(new std::vector<CacheTexture*>());
+ }
+ }
+}
+
+static void dumpTextures(String8& log, const char* tag,
+ const std::vector<CacheTexture*>& cacheTextures) {
+ for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+ CacheTexture* cacheTexture = cacheTextures[i];
+ if (cacheTexture && cacheTexture->getPixelBuffer()) {
+ uint32_t free = cacheTexture->calculateFreeMemory();
+ uint32_t total = cacheTexture->getPixelBuffer()->getSize();
+ log.appendFormat(" %-4s texture %d %8d / %8d\n", tag, i, total - free, total);
+ }
+ }
+}
+
+void FontRenderer::dumpMemoryUsage(String8& log) const {
+ const uint32_t sizeA8 = getCacheSize(GL_ALPHA);
+ const uint32_t usedA8 = sizeA8 - getFreeCacheSize(GL_ALPHA);
+ const uint32_t sizeRGBA = getCacheSize(GL_RGBA);
+ const uint32_t usedRGBA = sizeRGBA - getFreeCacheSize(GL_RGBA);
+ log.appendFormat(" FontRenderer A8 %8d / %8d\n", usedA8, sizeA8);
+ dumpTextures(log, "A8", cacheTexturesForFormat(GL_ALPHA));
+ log.appendFormat(" FontRenderer RGBA %8d / %8d\n", usedRGBA, sizeRGBA);
+ dumpTextures(log, "RGBA", cacheTexturesForFormat(GL_RGBA));
+ log.appendFormat(" FontRenderer total %8d / %8d\n", usedA8 + usedRGBA, sizeA8 + sizeRGBA);
+}
+
+uint32_t FontRenderer::getCacheSize(GLenum format) const {
+ return calculateCacheSize(cacheTexturesForFormat(format));
+}
+
+uint32_t FontRenderer::getFreeCacheSize(GLenum format) const {
+ return calculateFreeCacheSize(cacheTexturesForFormat(format));
+}
+
+uint32_t FontRenderer::getSize() const {
+ return getCacheSize(GL_ALPHA) + getCacheSize(GL_RGBA);
}
}; // namespace uirenderer
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 578beaa..e836c20 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -20,8 +20,12 @@
#include "font/CacheTexture.h"
#include "font/CachedGlyphInfo.h"
#include "font/Font.h"
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+#include "font/FontCacheHistoryTracker.h"
+#endif
#include <utils/LruCache.h>
+#include <utils/String8.h>
#include <utils/StrongPointer.h>
#include <SkPaint.h>
@@ -117,7 +121,12 @@
mLinearFiltering = linearFiltering;
}
- uint32_t getCacheSize(GLenum format) const;
+ uint32_t getSize() const;
+ void dumpMemoryUsage(String8& log) const;
+
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+ FontCacheHistoryTracker& historyTracker() { return mHistoryTracker; }
+#endif
private:
friend class Font;
@@ -160,6 +169,10 @@
mUploadTexture = true;
}
+ const std::vector<CacheTexture*>& cacheTexturesForFormat(GLenum format) const;
+ uint32_t getCacheSize(GLenum format) const;
+ uint32_t getFreeCacheSize(GLenum format) const;
+
uint32_t mSmallCacheWidth;
uint32_t mSmallCacheHeight;
uint32_t mLargeCacheWidth;
@@ -184,6 +197,10 @@
bool mLinearFiltering;
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+ FontCacheHistoryTracker mHistoryTracker;
+#endif
+
#ifdef ANDROID_ENABLE_RENDERSCRIPT
// RS constructs
RSC::sp<RSC::RS> mRs;
diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h
index 5813e7f..bd27a1a 100644
--- a/libs/hwui/GammaFontRenderer.h
+++ b/libs/hwui/GammaFontRenderer.h
@@ -22,6 +22,8 @@
#include <SkPaint.h>
+#include <utils/String8.h>
+
namespace android {
namespace uirenderer {
@@ -46,8 +48,16 @@
return *mRenderer;
}
- uint32_t getFontRendererSize(GLenum format) const {
- return mRenderer ? mRenderer->getCacheSize(format) : 0;
+ void dumpMemoryUsage(String8& log) const {
+ if (mRenderer) {
+ mRenderer->dumpMemoryUsage(log);
+ } else {
+ log.appendFormat("FontRenderer doesn't exist.\n");
+ }
+ }
+
+ uint32_t getSize() const {
+ return mRenderer ? mRenderer->getSize() : 0;
}
void endPrecaching();
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 65a8ebb..ddca122 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -32,7 +32,8 @@
namespace uirenderer {
static CopyResult copyTextureInto(Caches& caches, RenderState& renderState,
- Texture& sourceTexture, Matrix4& texTransform, SkBitmap* bitmap) {
+ Texture& sourceTexture, Matrix4& texTransform, const Rect& srcRect,
+ SkBitmap* bitmap) {
int destWidth = bitmap->width();
int destHeight = bitmap->height();
if (destWidth > caches.maxTextureSize
@@ -100,11 +101,19 @@
renderState.blend().syncEnabled();
renderState.stencil().disable();
+ Matrix4 croppedTexTransform(texTransform);
+ if (!srcRect.isEmpty()) {
+ croppedTexTransform.loadTranslate(srcRect.left / sourceTexture.width(),
+ srcRect.top / sourceTexture.height(), 0);
+ croppedTexTransform.scale(srcRect.getWidth() / sourceTexture.width(),
+ srcRect.getHeight() / sourceTexture.height(), 1);
+ croppedTexTransform.multiply(texTransform);
+ }
Glop glop;
GlopBuilder(renderState, caches, &glop)
.setRoundRectClipState(nullptr)
.setMeshTexturedUnitQuad(nullptr)
- .setFillExternalTexture(sourceTexture, texTransform)
+ .setFillExternalTexture(sourceTexture, croppedTexTransform)
.setTransform(Matrix4::identity(), TransformFlags::None)
.setModelViewMapUnitToRect(Rect(destWidth, destHeight))
.build();
@@ -126,7 +135,8 @@
}
CopyResult Readback::copySurfaceInto(renderthread::RenderThread& renderThread,
- Surface& surface, SkBitmap* bitmap) {
+ Surface& surface, const Rect& srcRect, SkBitmap* bitmap) {
+ ATRACE_CALL();
renderThread.eglManager().initialize();
Caches& caches = Caches::getInstance();
@@ -190,7 +200,7 @@
sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0 /* total lie */);
CopyResult copyResult = copyTextureInto(caches, renderThread.renderState(),
- sourceTexture, texTransform, bitmap);
+ sourceTexture, texTransform, srcRect, bitmap);
sourceTexture.deleteTexture();
// All we're flushing & finishing is the deletion of the texture since
// copyTextureInto already did a major flush & finish as an implicit
@@ -202,8 +212,9 @@
CopyResult Readback::copyTextureLayerInto(renderthread::RenderThread& renderThread,
Layer& layer, SkBitmap* bitmap) {
+ ATRACE_CALL();
return copyTextureInto(Caches::getInstance(), renderThread.renderState(),
- layer.getTexture(), layer.getTexTransform(), bitmap);
+ layer.getTexture(), layer.getTexTransform(), Rect(), bitmap);
}
} // namespace uirenderer
diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h
index bd73734..55c943c 100644
--- a/libs/hwui/Readback.h
+++ b/libs/hwui/Readback.h
@@ -17,6 +17,7 @@
#pragma once
#include "renderthread/RenderThread.h"
+#include "Rect.h"
#include <SkBitmap.h>
#include <gui/Surface.h>
@@ -42,7 +43,7 @@
* Copies the surface's most recently queued buffer into the provided bitmap.
*/
static CopyResult copySurfaceInto(renderthread::RenderThread& renderThread,
- Surface& surface, SkBitmap* bitmap);
+ Surface& surface, const Rect& srcRect, SkBitmap* bitmap);
/**
* Copies the TextureLayer's texture content (thus, the currently rendering buffer) into the
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 09775496..a3c9a39 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -179,9 +179,10 @@
static inline SkCanvas::SaveLayerFlags layerFlags(SaveFlags::Flags flags) {
SkCanvas::SaveLayerFlags layerFlags = 0;
- if (!(flags & SaveFlags::HasAlphaLayer)) {
- layerFlags |= SkCanvas::kIsOpaque_SaveLayerFlag;
- }
+ // We intentionally ignore the SaveFlags::HasAlphaLayer and
+ // SkCanvas::kIsOpaque_SaveLayerFlag flags because HWUI ignores it
+ // and our Android client may use it incorrectly.
+ // In Skia, this flag is purely for performance optimization.
if (!(flags & SaveFlags::ClipToLayer)) {
layerFlags |= SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag;
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index fcdde45..49e9f65 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -330,5 +330,17 @@
return false;
}
+uint32_t CacheTexture::calculateFreeMemory() const {
+ CacheBlock* cacheBlock = mCacheBlocks;
+ uint32_t free = 0;
+ // currently only two formats are supported: GL_ALPHA or GL_RGBA;
+ uint32_t bpp = mFormat == GL_RGBA ? 4 : 1;
+ while (cacheBlock) {
+ free += bpp * cacheBlock->mWidth * cacheBlock->mHeight;
+ cacheBlock = cacheBlock->mNext;
+ }
+ return free;
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h
index 4dfb41d..6750a8a 100644
--- a/libs/hwui/font/CacheTexture.h
+++ b/libs/hwui/font/CacheTexture.h
@@ -178,6 +178,8 @@
return mCurrentQuad == mMaxQuadCount;
}
+ uint32_t calculateFreeMemory() const;
+
private:
void setDirty(bool dirty);
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index 9c812bc..24d497c 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -408,9 +408,15 @@
if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
int penX = x + (int) roundf(positions[(glyphsCount << 1)]);
int penY = y + (int) roundf(positions[(glyphsCount << 1) + 1]);
-
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+ mState->historyTracker().glyphRendered(cachedGlyph, penX, penY);
+#endif
(*this.*render)(cachedGlyph, penX, penY,
bitmap, bitmapW, bitmapH, bounds, positions);
+ } else {
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+ mState->historyTracker().glyphRendered(cachedGlyph, -1, -1);
+#endif
}
glyphsCount++;
diff --git a/libs/hwui/font/FontCacheHistoryTracker.cpp b/libs/hwui/font/FontCacheHistoryTracker.cpp
new file mode 100644
index 0000000..a2bfb27
--- /dev/null
+++ b/libs/hwui/font/FontCacheHistoryTracker.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FontCacheHistoryTracker.h"
+
+#include "CachedGlyphInfo.h"
+#include "CacheTexture.h"
+
+namespace android {
+namespace uirenderer {
+
+void FontCacheHistoryTracker::dumpCachedGlyph(String8& log, const CachedGlyph& glyph) {
+ log.appendFormat("glyph (texture %p, position: (%d, %d), size: %dx%d, gen: %d)", glyph.texture,
+ glyph.startX, glyph.startY, glyph.bitmapW, glyph.bitmapH, glyph.generation);
+}
+
+void FontCacheHistoryTracker::dumpRenderEntry(String8& log, const RenderEntry& entry) {
+ if (entry.penX == -1 && entry.penY == -1) {
+ log.appendFormat(" glyph skipped in gen: %d\n", entry.glyph.generation);
+ } else {
+ log.appendFormat(" rendered ");
+ dumpCachedGlyph(log, entry.glyph);
+ log.appendFormat(" at (%d, %d)\n", entry.penX, entry.penY);
+ }
+}
+
+void FontCacheHistoryTracker::dumpUploadEntry(String8& log, const CachedGlyph& glyph) {
+ if (glyph.bitmapW == 0 && glyph.bitmapH == 0) {
+ log.appendFormat(" cleared cachetexture %p in gen %d\n", glyph.texture,
+ glyph.generation);
+ } else {
+ log.appendFormat(" uploaded ");
+ dumpCachedGlyph(log, glyph);
+ log.appendFormat("\n");
+ }
+}
+
+void FontCacheHistoryTracker::dump(String8& log) const {
+ log.appendFormat("FontCacheHistory: \n");
+ log.appendFormat(" Upload history: \n");
+ for (size_t i = 0; i < mUploadHistory.size(); i++) {
+ dumpUploadEntry(log, mUploadHistory[i]);
+ }
+ log.appendFormat(" Render history: \n");
+ for (size_t i = 0; i < mRenderHistory.size(); i++) {
+ dumpRenderEntry(log, mRenderHistory[i]);
+ }
+}
+
+void FontCacheHistoryTracker::glyphRendered(CachedGlyphInfo* glyphInfo, int penX, int penY) {
+ RenderEntry& entry = mRenderHistory.next();
+ entry.glyph.generation = generation;
+ entry.glyph.texture = glyphInfo->mCacheTexture;
+ entry.glyph.startX = glyphInfo->mStartX;
+ entry.glyph.startY = glyphInfo->mStartY;
+ entry.glyph.bitmapW = glyphInfo->mBitmapWidth;
+ entry.glyph.bitmapH = glyphInfo->mBitmapHeight;
+ entry.penX = penX;
+ entry.penY = penY;
+}
+
+void FontCacheHistoryTracker::glyphUploaded(CacheTexture* texture, uint32_t x, uint32_t y,
+ uint16_t glyphW, uint16_t glyphH) {
+ CachedGlyph& glyph = mUploadHistory.next();
+ glyph.generation = generation;
+ glyph.texture = texture;
+ glyph.startX = x;
+ glyph.startY = y;
+ glyph.bitmapW = glyphW;
+ glyph.bitmapH = glyphH;
+}
+
+void FontCacheHistoryTracker::glyphsCleared(CacheTexture* texture) {
+ CachedGlyph& glyph = mUploadHistory.next();
+ glyph.generation = generation;
+ glyph.texture = texture;
+ glyph.startX = 0;
+ glyph.startY = 0;
+ glyph.bitmapW = 0;
+ glyph.bitmapH = 0;
+}
+
+void FontCacheHistoryTracker::frameCompleted() {
+ generation++;
+}
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/font/FontCacheHistoryTracker.h b/libs/hwui/font/FontCacheHistoryTracker.h
new file mode 100644
index 0000000..f1d9b9f
--- /dev/null
+++ b/libs/hwui/font/FontCacheHistoryTracker.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include "../utils/RingBuffer.h"
+
+#include <utils/String8.h>
+
+namespace android {
+namespace uirenderer {
+
+class CacheTexture;
+struct CachedGlyphInfo;
+
+// Tracks glyph uploads and recent rendered/skipped glyphs, so it can give an idea
+// what a missing character is: skipped glyph, wrong coordinates in cache texture etc.
+class FontCacheHistoryTracker {
+public:
+ void glyphRendered(CachedGlyphInfo*, int penX, int penY);
+ void glyphUploaded(CacheTexture*, uint32_t x, uint32_t y, uint16_t glyphW, uint16_t glyphH);
+ void glyphsCleared(CacheTexture*);
+ void frameCompleted();
+
+ void dump(String8& log) const;
+private:
+ struct CachedGlyph {
+ void* texture;
+ uint16_t generation;
+ uint16_t startX;
+ uint16_t startY;
+ uint16_t bitmapW;
+ uint16_t bitmapH;
+ };
+
+ struct RenderEntry {
+ CachedGlyph glyph;
+ int penX;
+ int penY;
+ };
+
+ static void dumpCachedGlyph(String8& log, const CachedGlyph& glyph);
+ static void dumpRenderEntry(String8& log, const RenderEntry& entry);
+ static void dumpUploadEntry(String8& log, const CachedGlyph& glyph);
+
+ RingBuffer<RenderEntry, 300> mRenderHistory;
+ RingBuffer<CachedGlyph, 120> mUploadHistory;
+ uint16_t generation = 0;
+};
+
+}; // namespace uirenderer
+}; // namespace android
\ No newline at end of file
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 289a72f..43471e5 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -412,6 +412,11 @@
}
GpuMemoryTracker::onFrameCompleted();
+#ifdef BUGREPORT_FONT_CACHE_USAGE
+ Caches& caches = Caches::getInstance();
+ caches.fontRenderer.getFontRenderer().historyTracker().frameCompleted();
+#endif
+
}
// Called by choreographer to do an RT-driven animation
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 3edd423..dcbc980 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -617,17 +617,19 @@
post(task);
}
-CREATE_BRIDGE3(copySurfaceInto, RenderThread* thread,
- Surface* surface, SkBitmap* bitmap) {
+CREATE_BRIDGE4(copySurfaceInto, RenderThread* thread,
+ Surface* surface, Rect srcRect, SkBitmap* bitmap) {
return (void*) Readback::copySurfaceInto(*args->thread,
- *args->surface, args->bitmap);
+ *args->surface, args->srcRect, args->bitmap);
}
-int RenderProxy::copySurfaceInto(sp<Surface>& surface, SkBitmap* bitmap) {
+int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top,
+ int right, int bottom, SkBitmap* bitmap) {
SETUP_TASK(copySurfaceInto);
args->bitmap = bitmap;
args->surface = surface.get();
args->thread = &RenderThread::getInstance();
+ args->srcRect.set(left, top, right, bottom);
return static_cast<int>(
reinterpret_cast<intptr_t>( staticPostAndWait(task) ));
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index d812970..d4aaea6 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -127,7 +127,8 @@
ANDROID_API void removeFrameMetricsObserver(FrameMetricsObserver* observer);
ANDROID_API long getDroppedFrameReportCount();
- ANDROID_API static int copySurfaceInto(sp<Surface>& surface, SkBitmap* bitmap);
+ ANDROID_API static int copySurfaceInto(sp<Surface>& surface,
+ int left, int top, int right, int bottom, SkBitmap* bitmap);
ANDROID_API static void prepareToDraw(const SkBitmap& bitmap);
private:
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index c006d99..f3b7b51 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -117,6 +117,7 @@
std::unique_ptr<TestScene> scene(info.createScene(opts));
+ Properties::forceDrawFrame = true;
TestContext testContext;
testContext.setRenderOffscreen(opts.renderOffscreen);
diff --git a/libs/hwui/tests/scripts/prep_buller.sh b/libs/hwui/tests/scripts/prep_buller.sh
index b2f68bd..65292c3 100755
--- a/libs/hwui/tests/scripts/prep_buller.sh
+++ b/libs/hwui/tests/scripts/prep_buller.sh
@@ -38,12 +38,27 @@
adb shell "echo 1 > /sys/class/kgsl/kgsl-3d0/force_clk_on"
adb shell "echo 10000 > /sys/class/kgsl/kgsl-3d0/idle_timer"
-#0 762 1144 1525 2288 3509 4173 5271 5928 7904 9887 11863
+# angler: 0 762 1144 1525 2288 3509 4173 5271 5928 7904 9887 11863
adb shell "echo 11863 > /sys/class/devfreq/qcom,gpubw.70/min_freq" &> /dev/null
-adb shell "echo 11863 > /sys/class/devfreq/qcom,gpubw.19/min_freq" &> /dev/null
+# bullhead: 0 762 1144 1525 2288 3509 4173 5271 5928 7102
+adb shell "echo 7102 > /sys/class/devfreq/qcom,gpubw.19/min_freq" &> /dev/null
-#600000000 510000000 450000000 390000000 305000000 180000000
-echo "performance mode, 305 MHz"
+
+board=$(adb shell "getprop ro.product.board")
+freq=0
+if [ "$board" = "bullhead" ]
+then
+ #600000000 490000000 450000000 367000000 300000000 180000000
+ freq=300000000
+else
+ #600000000 510000000 450000000 390000000 305000000 180000000
+ freq=305000000
+fi
+echo "performance mode, $freq Hz"
adb shell "echo performance > /sys/class/kgsl/kgsl-3d0/devfreq/governor"
-adb shell "echo 305000000 > /sys/class/kgsl/kgsl-3d0/devfreq/min_freq"
-adb shell "echo 305000000 > /sys/class/kgsl/kgsl-3d0/devfreq/max_freq"
+adb shell "echo $freq > /sys/class/kgsl/kgsl-3d0/devfreq/min_freq"
+adb shell "echo $freq > /sys/class/kgsl/kgsl-3d0/devfreq/max_freq"
+
+adb shell "echo 4 > /sys/class/kgsl/kgsl-3d0/min_pwrlevel"
+adb shell "echo 4 > /sys/class/kgsl/kgsl-3d0/max_pwrlevel"
+
diff --git a/libs/hwui/tests/scripts/stopruntime.sh b/libs/hwui/tests/scripts/stopruntime.sh
old mode 100644
new mode 100755
index bb8fcb6..85a91db
--- a/libs/hwui/tests/scripts/stopruntime.sh
+++ b/libs/hwui/tests/scripts/stopruntime.sh
@@ -22,6 +22,5 @@
adb shell kill $pid
done
adb shell setprop debug.egl.traceGpuCompletion 1
+adb shell setprop debug.sf.nobootanimation 1
adb shell daemonize surfaceflinger
-sleep 3
-adb shell setprop service.bootanim.exit 1
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 87a22ce..89b4fb2 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -36,6 +36,29 @@
namespace android {
+// --- WeakLooperCallback ---
+
+class WeakLooperCallback: public LooperCallback {
+protected:
+ virtual ~WeakLooperCallback() { }
+
+public:
+ WeakLooperCallback(const wp<LooperCallback>& callback) :
+ mCallback(callback) {
+ }
+
+ virtual int handleEvent(int fd, int events, void* data) {
+ sp<LooperCallback> callback = mCallback.promote();
+ if (callback != NULL) {
+ return callback->handleEvent(fd, events, data);
+ }
+ return 0; // the client is gone, remove the callback
+ }
+
+private:
+ wp<LooperCallback> mCallback;
+};
+
// --- PointerController ---
// Time to wait before starting the fade when the pointer is inactive.
@@ -57,10 +80,11 @@
const sp<Looper>& looper, const sp<SpriteController>& spriteController) :
mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
mHandler = new WeakMessageHandler(this);
+ mCallback = new WeakLooperCallback(this);
if (mDisplayEventReceiver.initCheck() == NO_ERROR) {
mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK,
- Looper::EVENT_INPUT, this, nullptr);
+ Looper::EVENT_INPUT, mCallback, nullptr);
} else {
ALOGE("Failed to initialize DisplayEventReceiver.");
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 99292d7..4794f3d 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -144,6 +144,7 @@
sp<Looper> mLooper;
sp<SpriteController> mSpriteController;
sp<WeakMessageHandler> mHandler;
+ sp<LooperCallback> mCallback;
DisplayEventReceiver mDisplayEventReceiver;
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 5286f8f..89709ee 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -24,6 +24,7 @@
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
+import android.util.SparseIntArray;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -170,6 +171,66 @@
public final static int USAGE_VIRTUAL_SOURCE = 15;
/**
+ * IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
+ * if applicable.
+ */
+
+ /**
+ * @hide
+ * Denotes a usage for notifications that do not expect immediate intervention from the user,
+ * will be muted when the Zen mode disables notifications
+ * @see #SUPPRESSIBLE_USAGES
+ */
+ public final static int SUPPRESSIBLE_NOTIFICATION = 1;
+ /**
+ * @hide
+ * Denotes a usage for notifications that do expect immediate intervention from the user,
+ * will be muted when the Zen mode disables calls
+ * @see #SUPPRESSIBLE_USAGES
+ */
+ public final static int SUPPRESSIBLE_CALL = 2;
+
+ /**
+ * @hide
+ * Array of all usage types for calls and notifications to assign the suppression behavior,
+ * used by the Zen mode restrictions.
+ * @see com.android.server.notification.ZenModeHelper
+ */
+ public static final SparseIntArray SUPPRESSIBLE_USAGES;
+
+ static {
+ SUPPRESSIBLE_USAGES = new SparseIntArray();
+ SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION, SUPPRESSIBLE_NOTIFICATION);
+ SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_RINGTONE, SUPPRESSIBLE_CALL);
+ SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_COMMUNICATION_REQUEST,SUPPRESSIBLE_CALL);
+ SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_COMMUNICATION_INSTANT,SUPPRESSIBLE_NOTIFICATION);
+ SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_COMMUNICATION_DELAYED,SUPPRESSIBLE_NOTIFICATION);
+ SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_EVENT, SUPPRESSIBLE_NOTIFICATION);
+ }
+
+ /**
+ * @hide
+ * Array of all usage types exposed in the SDK that applications can use.
+ */
+ public final static int[] SDK_USAGES = {
+ USAGE_UNKNOWN,
+ USAGE_MEDIA,
+ USAGE_VOICE_COMMUNICATION,
+ USAGE_VOICE_COMMUNICATION_SIGNALLING,
+ USAGE_ALARM,
+ USAGE_NOTIFICATION,
+ USAGE_NOTIFICATION_RINGTONE,
+ USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
+ USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
+ USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
+ USAGE_NOTIFICATION_EVENT,
+ USAGE_ASSISTANCE_ACCESSIBILITY,
+ USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+ USAGE_ASSISTANCE_SONIFICATION,
+ USAGE_GAME
+ };
+
+ /**
* Flag defining a behavior where the audibility of the sound will be ensured by the system.
*/
public final static int FLAG_AUDIBILITY_ENFORCED = 0x1 << 0;
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 2cbeb3a2..ed71849 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -241,6 +241,10 @@
* same {@link Surface} can be reused with a different API once the first source is
* disconnected from the {@link Surface}.</p>
*
+ * <p>Please note that holding on to the Surface object returned by this method is not enough
+ * to keep its parent ImageReader from being reclaimed. In that sense, a Surface acts like a
+ * {@link java.lang.ref.WeakReference weak reference} to the ImageReader that provides it.</p>
+ *
* @return A {@link Surface} to use for a drawing target for various APIs.
*/
public Surface getSurface() {
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 9e560d5..4d332a8 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -510,6 +510,11 @@
/**
* Advance to the next sample. Returns false if no more sample data
* is available (end of stream).
+ *
+ * When extracting a local file, the behaviors of {@link #advance} and
+ * {@link #readSampleData} are undefined in presence of concurrent
+ * writes to the same local file; more specifically, end of stream
+ * could be signalled earlier than expected.
*/
public native boolean advance();
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index dd26799..bf1559d 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1641,7 +1641,8 @@
* (i.e. reaches the end of the stream).
* The media framework will attempt to transition from this player to
* the next as seamlessly as possible. The next player can be set at
- * any time before completion. The next player must be prepared by the
+ * any time before completion, but shall be after setDataSource has been
+ * called successfully. The next player must be prepared by the
* app, and the application should not call start() on it.
* The next MediaPlayer must be different from 'this'. An exception
* will be thrown if next == this.
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 5b4443b..96ea834 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -219,26 +219,12 @@
}
if (mBluetoothA2dpRoute != null) {
- final boolean a2dpEnabled = isBluetoothA2dpOn();
- if (mainType != AudioRoutesInfo.MAIN_SPEAKER &&
- mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
- selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
- } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
- a2dpEnabled) {
+ if (mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) {
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false);
}
}
}
- boolean isBluetoothA2dpOn() {
- try {
- return mAudioService.isBluetoothA2dpOn();
- } catch (RemoteException e) {
- Log.e(TAG, "Error querying Bluetooth A2DP state", e);
- return false;
- }
- }
-
void updateDiscoveryRequest() {
// What are we looking for today?
int routeTypes = 0;
@@ -907,6 +893,11 @@
static void selectRouteStatic(int types, @NonNull RouteInfo route, boolean explicit) {
Log.v(TAG, "Selecting route: " + route);
assert(route != null);
+ if (route == sStatic.mDefaultAudioVideo && sStatic.mBluetoothA2dpRoute != null) {
+ Log.i(TAG, "Change the route to a BT route: " + sStatic.mBluetoothA2dpRoute
+ + "\nDo not select the default route when a BT route is available.");
+ route = sStatic.mBluetoothA2dpRoute;
+ }
final RouteInfo oldRoute = sStatic.mSelectedRoute;
if (oldRoute == route) return;
if (!route.matchesTypes(types)) {
@@ -916,16 +907,6 @@
return;
}
- final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
- if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
- (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
- try {
- sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
- } catch (RemoteException e) {
- Log.e(TAG, "Error changing Bluetooth A2DP state", e);
- }
- }
-
final WifiDisplay activeDisplay =
sStatic.mDisplayService.getWifiDisplayStatus().getActiveDisplay();
final boolean oldRouteHasAddress = oldRoute != null && oldRoute.mDeviceAddress != null;
@@ -965,7 +946,7 @@
static void selectDefaultRouteStatic() {
// TODO: Be smarter about the route types here; this selects for all valid.
if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute
- && sStatic.mBluetoothA2dpRoute != null && sStatic.isBluetoothA2dpOn()) {
+ && sStatic.mBluetoothA2dpRoute != null) {
selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false);
} else {
selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false);
@@ -1297,12 +1278,7 @@
selectedRoute == sStatic.mDefaultAudioVideo) {
dispatchRouteVolumeChanged(selectedRoute);
} else if (sStatic.mBluetoothA2dpRoute != null) {
- try {
- dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
- sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
- } catch (RemoteException e) {
- Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
- }
+ dispatchRouteVolumeChanged(sStatic.mBluetoothA2dpRoute);
} else {
dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo);
}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index f6e2a0c..ddfa025 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -21,12 +21,14 @@
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.SQLException;
import android.drm.DrmManagerClient;
import android.graphics.BitmapFactory;
import android.mtp.MtpConstants;
import android.net.Uri;
+import android.os.Build;
import android.os.Environment;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -153,6 +155,11 @@
private static final String MUSIC_DIR = "/music/";
private static final String PODCAST_DIR = "/podcasts/";
+ public static final String SCANNED_BUILD_PREFS_NAME = "MediaScanBuild";
+ public static final String LAST_INTERNAL_SCAN_FINGERPRINT = "lastScanFingerprint";
+ private static final String SYSTEM_SOUNDS_DIR = "/system/media/audio";
+ private static String sLastInternalScanFingerprint;
+
private static final String[] ID3_GENRES = {
// ID3v1 Genres
"Blues",
@@ -402,6 +409,13 @@
mMediaProvider = mContext.getContentResolver()
.acquireContentProviderClient(MediaStore.AUTHORITY);
+ if (sLastInternalScanFingerprint == null) {
+ final SharedPreferences scanSettings =
+ mContext.getSharedPreferences(SCANNED_BUILD_PREFS_NAME, Context.MODE_PRIVATE);
+ sLastInternalScanFingerprint =
+ scanSettings.getString(LAST_INTERNAL_SCAN_FINGERPRINT, new String());
+ }
+
mAudioUri = Audio.Media.getContentUri(volumeName);
mVideoUri = Video.Media.getContentUri(volumeName);
mImagesUri = Images.Media.getContentUri(volumeName);
@@ -585,16 +599,24 @@
entry.mRowId = 0;
}
- if (entry.mPath != null &&
- ((!mDefaultNotificationSet &&
+ if (entry.mPath != null) {
+ if (((!mDefaultNotificationSet &&
doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename))
|| (!mDefaultRingtoneSet &&
doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename))
|| (!mDefaultAlarmSet &&
doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)))) {
- Log.w(TAG, "forcing rescan of " + entry.mPath +
- "since ringtone setting didn't finish");
- scanAlways = true;
+ Log.w(TAG, "forcing rescan of " + entry.mPath +
+ "since ringtone setting didn't finish");
+ scanAlways = true;
+ } else if (isSystemSoundWithMetadata(entry.mPath)
+ && !Build.FINGERPRINT.equals(sLastInternalScanFingerprint)) {
+ // file is located on the system partition where the date cannot be trusted:
+ // rescan if the build fingerprint has changed since the last scan.
+ Log.i(TAG, "forcing rescan of " + entry.mPath
+ + " since build fingerprint changed");
+ scanAlways = true;
+ }
}
// rescan for metadata if file was modified since last scan
@@ -1128,6 +1150,15 @@
}; // end of anonymous MediaScannerClient instance
+ private static boolean isSystemSoundWithMetadata(String path) {
+ if (path.startsWith(SYSTEM_SOUNDS_DIR + ALARMS_DIR)
+ || path.startsWith(SYSTEM_SOUNDS_DIR + RINGTONES_DIR)
+ || path.startsWith(SYSTEM_SOUNDS_DIR + NOTIFICATIONS_DIR)) {
+ return true;
+ }
+ return false;
+ }
+
private String settingSetIndicatorName(String base) {
return base + "_set";
}
@@ -1252,16 +1283,6 @@
}
}
- private boolean inScanDirectory(String path, String[] directories) {
- for (int i = 0; i < directories.length; i++) {
- String directory = directories[i];
- if (path.startsWith(directory)) {
- return true;
- }
- }
- return false;
- }
-
private void pruneDeadThumbnailFiles() {
HashSet<String> existingFiles = new HashSet<String>();
String directory = "/sdcard/DCIM/.thumbnails";
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl
index d5115de..fbd3510 100644
--- a/media/java/android/media/midi/IMidiDeviceServer.aidl
+++ b/media/java/android/media/midi/IMidiDeviceServer.aidl
@@ -17,19 +17,18 @@
package android.media.midi;
import android.media.midi.MidiDeviceInfo;
-import android.os.ParcelFileDescriptor;
/** @hide */
interface IMidiDeviceServer
{
- ParcelFileDescriptor openInputPort(IBinder token, int portNumber);
- ParcelFileDescriptor openOutputPort(IBinder token, int portNumber);
+ FileDescriptor openInputPort(IBinder token, int portNumber);
+ FileDescriptor openOutputPort(IBinder token, int portNumber);
void closePort(IBinder token);
void closeDevice();
// connects the input port pfd to the specified output port
// Returns the PID of the called process.
- int connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber);
+ int connectPorts(IBinder token, in FileDescriptor fd, int outputPortNumber);
MidiDeviceInfo getDeviceInfo();
void setDeviceInfo(in MidiDeviceInfo deviceInfo);
diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java
index da44ca6..f79cd06 100644
--- a/media/java/android/media/midi/MidiDevice.java
+++ b/media/java/android/media/midi/MidiDevice.java
@@ -18,7 +18,6 @@
import android.os.Binder;
import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
@@ -28,6 +27,7 @@
import libcore.io.IoUtils;
import java.io.Closeable;
+import java.io.FileDescriptor;
import java.io.IOException;
/**
@@ -129,11 +129,11 @@
}
try {
IBinder token = new Binder();
- ParcelFileDescriptor pfd = mDeviceServer.openInputPort(token, portNumber);
- if (pfd == null) {
+ FileDescriptor fd = mDeviceServer.openInputPort(token, portNumber);
+ if (fd == null) {
return null;
}
- return new MidiInputPort(mDeviceServer, token, pfd, portNumber);
+ return new MidiInputPort(mDeviceServer, token, fd, portNumber);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in openInputPort");
return null;
@@ -155,11 +155,11 @@
}
try {
IBinder token = new Binder();
- ParcelFileDescriptor pfd = mDeviceServer.openOutputPort(token, portNumber);
- if (pfd == null) {
+ FileDescriptor fd = mDeviceServer.openOutputPort(token, portNumber);
+ if (fd == null) {
return null;
}
- return new MidiOutputPort(mDeviceServer, token, pfd, portNumber);
+ return new MidiOutputPort(mDeviceServer, token, fd, portNumber);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in openOutputPort");
return null;
@@ -186,20 +186,20 @@
return null;
}
- ParcelFileDescriptor pfd = inputPort.claimFileDescriptor();
- if (pfd == null) {
+ FileDescriptor fd = inputPort.claimFileDescriptor();
+ if (fd == null) {
return null;
}
try {
IBinder token = new Binder();
- int calleePid = mDeviceServer.connectPorts(token, pfd, outputPortNumber);
- // If the service is a different Process then it will duplicate the pfd
+ int calleePid = mDeviceServer.connectPorts(token, fd, outputPortNumber);
+ // If the service is a different Process then it will duplicate the fd
// and we can safely close this one.
- // But if the service is in the same Process then closing the pfd will
+ // But if the service is in the same Process then closing the fd will
// kill the connection. So don't do that.
if (calleePid != Process.myPid()) {
// close our copy of the file descriptor
- IoUtils.closeQuietly(pfd);
+ IoUtils.closeQuietly(fd);
}
return new MidiConnection(token, inputPort);
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index 4c49f67..eaa8654 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -18,9 +18,10 @@
import android.os.Binder;
import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
@@ -31,6 +32,7 @@
import libcore.io.IoUtils;
import java.io.Closeable;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -167,11 +169,22 @@
}
}
+ private static FileDescriptor[] createSeqPacketSocketPair() throws IOException {
+ try {
+ final FileDescriptor fd0 = new FileDescriptor();
+ final FileDescriptor fd1 = new FileDescriptor();
+ Os.socketpair(OsConstants.AF_UNIX, OsConstants.SOCK_SEQPACKET, 0, fd0, fd1);
+ return new FileDescriptor[] { fd0, fd1 };
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
// Binder interface stub for receiving connection requests from clients
private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
@Override
- public ParcelFileDescriptor openInputPort(IBinder token, int portNumber) {
+ public FileDescriptor openInputPort(IBinder token, int portNumber) {
if (mDeviceInfo.isPrivate()) {
if (Binder.getCallingUid() != Process.myUid()) {
throw new SecurityException("Can't access private device from different UID");
@@ -190,8 +203,7 @@
}
try {
- ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
- OsConstants.SOCK_SEQPACKET);
+ FileDescriptor[] pair = createSeqPacketSocketPair();
MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
mInputPortOutputPorts[portNumber] = outputPort;
outputPort.connect(mInputPortReceivers[portNumber]);
@@ -203,14 +215,14 @@
updateDeviceStatus();
return pair[1];
} catch (IOException e) {
- Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
+ Log.e(TAG, "unable to create FileDescriptors in openInputPort");
return null;
}
}
}
@Override
- public ParcelFileDescriptor openOutputPort(IBinder token, int portNumber) {
+ public FileDescriptor openOutputPort(IBinder token, int portNumber) {
if (mDeviceInfo.isPrivate()) {
if (Binder.getCallingUid() != Process.myUid()) {
throw new SecurityException("Can't access private device from different UID");
@@ -223,14 +235,13 @@
}
try {
- ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
- OsConstants.SOCK_SEQPACKET);
+ FileDescriptor[] pair = createSeqPacketSocketPair();
MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
// Undo the default blocking-mode of the server-side socket for
// physical devices to avoid stalling the Java device handler if
// client app code gets stuck inside 'onSend' handler.
if (mDeviceInfo.getType() != MidiDeviceInfo.TYPE_VIRTUAL) {
- IoUtils.setBlocking(pair[0].getFileDescriptor(), false);
+ IoUtils.setBlocking(pair[0], false);
}
MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
synchronized (dispatcher) {
@@ -250,7 +261,7 @@
}
return pair[1];
} catch (IOException e) {
- Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
+ Log.e(TAG, "unable to create FileDescriptors in openOutputPort");
return null;
}
}
@@ -281,9 +292,9 @@
}
@Override
- public int connectPorts(IBinder token, ParcelFileDescriptor pfd,
+ public int connectPorts(IBinder token, FileDescriptor fd,
int outputPortNumber) {
- MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber);
+ MidiInputPort inputPort = new MidiInputPort(fd, outputPortNumber);
MidiDispatcher dispatcher = mOutputPortDispatchers[outputPortNumber];
synchronized (dispatcher) {
dispatcher.getSender().connect(inputPort);
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index db41b10..98ec779 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -17,7 +17,6 @@
package android.media.midi;
import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
@@ -26,6 +25,7 @@
import libcore.io.IoUtils;
import java.io.Closeable;
+import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -38,7 +38,7 @@
private IMidiDeviceServer mDeviceServer;
private final IBinder mToken;
private final int mPortNumber;
- private ParcelFileDescriptor mParcelFileDescriptor;
+ private FileDescriptor mFileDescriptor;
private FileOutputStream mOutputStream;
private final CloseGuard mGuard = CloseGuard.get();
@@ -48,19 +48,19 @@
private final byte[] mBuffer = new byte[MidiPortImpl.MAX_PACKET_SIZE];
/* package */ MidiInputPort(IMidiDeviceServer server, IBinder token,
- ParcelFileDescriptor pfd, int portNumber) {
+ FileDescriptor fd, int portNumber) {
super(MidiPortImpl.MAX_PACKET_DATA_SIZE);
mDeviceServer = server;
mToken = token;
- mParcelFileDescriptor = pfd;
+ mFileDescriptor = fd;
mPortNumber = portNumber;
- mOutputStream = new FileOutputStream(pfd.getFileDescriptor());
+ mOutputStream = new FileOutputStream(fd);
mGuard.open("close");
}
- /* package */ MidiInputPort(ParcelFileDescriptor pfd, int portNumber) {
- this(null, null, pfd, portNumber);
+ /* package */ MidiInputPort(FileDescriptor fd, int portNumber) {
+ this(null, null, fd, portNumber);
}
/**
@@ -102,21 +102,21 @@
}
// used by MidiDevice.connectInputPort() to connect our socket directly to another device
- /* package */ ParcelFileDescriptor claimFileDescriptor() {
+ /* package */ FileDescriptor claimFileDescriptor() {
synchronized (mGuard) {
- ParcelFileDescriptor pfd;
+ FileDescriptor fd;
synchronized (mBuffer) {
- pfd = mParcelFileDescriptor;
- if (pfd == null) return null;
+ fd = mFileDescriptor;
+ if (fd == null) return null;
IoUtils.closeQuietly(mOutputStream);
- mParcelFileDescriptor = null;
+ mFileDescriptor = null;
mOutputStream = null;
}
// Set mIsClosed = true so we will not call mDeviceServer.closePort() in close().
// MidiDevice.MidiConnection.close() will do the cleanup instead.
mIsClosed = true;
- return pfd;
+ return fd;
}
}
@@ -136,9 +136,9 @@
if (mIsClosed) return;
mGuard.close();
synchronized (mBuffer) {
- if (mParcelFileDescriptor != null) {
- mParcelFileDescriptor.close();
- mParcelFileDescriptor = null;
+ if (mFileDescriptor != null) {
+ IoUtils.closeQuietly(mFileDescriptor);
+ mFileDescriptor = null;
}
if (mOutputStream != null) {
mOutputStream.close();
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 54c31e3..ce0402c 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -28,6 +28,7 @@
import libcore.io.IoUtils;
import java.io.Closeable;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
@@ -91,17 +92,17 @@
};
/* package */ MidiOutputPort(IMidiDeviceServer server, IBinder token,
- ParcelFileDescriptor pfd, int portNumber) {
+ FileDescriptor fd, int portNumber) {
mDeviceServer = server;
mToken = token;
mPortNumber = portNumber;
- mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(new ParcelFileDescriptor(fd));
mThread.start();
mGuard.open("close");
}
- /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) {
- this(null, null, pfd, portNumber);
+ /* package */ MidiOutputPort(FileDescriptor fd, int portNumber) {
+ this(null, null, fd, portNumber);
}
/**
diff --git a/media/java/android/media/midi/MidiPortImpl.java b/media/java/android/media/midi/MidiPortImpl.java
index 16fc214..1cd9ed2 100644
--- a/media/java/android/media/midi/MidiPortImpl.java
+++ b/media/java/android/media/midi/MidiPortImpl.java
@@ -34,7 +34,7 @@
public static final int PACKET_TYPE_FLUSH = 2;
/**
- * Maximum size of a packet that can pass through our ParcelFileDescriptor.
+ * Maximum size of a packet that can be passed between processes.
*/
public static final int MAX_PACKET_SIZE = 1024;
@@ -54,7 +54,7 @@
public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - DATA_PACKET_OVERHEAD;
/**
- * Utility function for packing MIDI data to be sent through our ParcelFileDescriptor
+ * Utility function for packing MIDI data to be passed between processes
*
* message byte array contains variable length MIDI message.
* messageSize is size of variable length MIDI message
@@ -84,7 +84,7 @@
}
/**
- * Utility function for packing a flush command to be sent through our ParcelFileDescriptor
+ * Utility function for packing a flush command to be passed between processes
*/
public static int packFlush(byte[] dest) {
dest[0] = PACKET_TYPE_FLUSH;
@@ -99,7 +99,7 @@
}
/**
- * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from other process
* returns the offset of the MIDI message in packed buffer
*/
public static int getDataOffset(byte[] buffer, int bufferLength) {
@@ -108,7 +108,7 @@
}
/**
- * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from other process
* returns size of MIDI data in packed buffer
*/
public static int getDataSize(byte[] buffer, int bufferLength) {
@@ -117,7 +117,7 @@
}
/**
- * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from other process
* unpacks timestamp from packed buffer
*/
public static long getPacketTimestamp(byte[] buffer, int bufferLength) {
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index a215493..4f3314c 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -46,7 +46,7 @@
void setExtras(in Bundle extras);
void setRatingType(int type);
void setRepeatMode(int repeatMode);
- void setShuffleMode(boolean shuffleMode);
+ void setShuffleModeEnabled(boolean enabled);
// These commands relate to volume handling
void setPlaybackToLocal(in AudioAttributes attributes);
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index b3c6d59..2f6e260 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -47,7 +47,7 @@
void onSeekTo(long pos);
void onRate(in Rating rating);
void onRepeatMode(int repeatMode);
- void onShuffleMode(boolean shuffleMode);
+ void onShuffleMode(boolean enabled);
void onCustomAction(String action, in Bundle args);
// These callbacks are for volume handling
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index b73f167..e92758c 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -55,7 +55,7 @@
Bundle getExtras();
int getRatingType();
int getRepeatMode();
- boolean getShuffleMode();
+ boolean isShuffleModeEnabled();
// These commands are for the TransportControls
void prepare();
@@ -76,6 +76,6 @@
void seekTo(long pos);
void rate(in Rating rating);
void repeatMode(int repeatMode);
- void shuffleMode(boolean shuffleMode);
+ void shuffleMode(boolean enabled);
void sendCustomAction(String action, in Bundle args);
}
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index ddff1b7..8cbf8e1 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -251,15 +251,15 @@
}
/**
- * Get the shuffle mode for this session.
+ * Return whether the shuffle mode is enabled for this session.
*
- * @return The latest shuffle mode set to the session, or false if not set.
+ * @return {@code true} if the shuffle mode is enabled, {@code false} if disabled or not set.
*/
- public boolean getShuffleMode() {
+ public boolean isShuffleModeEnabled() {
try {
- return mSessionBinder.getShuffleMode();
+ return mSessionBinder.isShuffleModeEnabled();
} catch (RemoteException e) {
- Log.wtf(TAG, "Error calling getShuffleMode.", e);
+ Log.wtf(TAG, "Error calling isShuffleModeEnabled.", e);
return false;
}
}
@@ -625,10 +625,9 @@
/**
* Override to handle changes to the shuffle mode.
*
- * @param shuffleMode The shuffle mode. {@code true} if _the_ shuffle mode is on,
- * {@code false} otherwise.
+ * @param enabled {@code true} if the shuffle mode is enabled, {@code false} otherwise.
*/
- public void onShuffleModeChanged(boolean shuffleMode) {
+ public void onShuffleModeChanged(boolean enabled) {
}
}
@@ -931,13 +930,13 @@
/**
* Set the shuffle mode for this session.
*
- * @param shuffleMode {@code true} if the shuffle mode is on, {@code false} otherwise.
+ * @param enabled {@code true} to enable the shuffle mode, {@code false} to disable.
*/
- public void setShuffleMode(boolean shuffleMode) {
+ public void setShuffleModeEnabled(boolean enabled) {
try {
- mSessionBinder.shuffleMode(shuffleMode);
+ mSessionBinder.shuffleMode(enabled);
} catch (RemoteException e) {
- Log.wtf(TAG, "Error calling shuffleQueue.", e);
+ Log.wtf(TAG, "Error calling shuffleMode.", e);
}
}
@@ -1151,10 +1150,10 @@
}
@Override
- public void onShuffleModeChanged(boolean shuffleMode) {
+ public void onShuffleModeChanged(boolean enabled) {
MediaController controller = mController.get();
if (controller != null) {
- controller.postMessage(MSG_UPDATE_SHUFFLE_MODE, shuffleMode, null);
+ controller.postMessage(MSG_UPDATE_SHUFFLE_MODE, enabled, null);
}
}
}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index cab5d93..84dc93a 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -501,16 +501,16 @@
/**
* Set the shuffle mode for this session.
* <p>
- * Note that if this method is not called before, {@link MediaController#getShuffleMode}
+ * Note that if this method is not called before, {@link MediaController#isShuffleModeEnabled}
* will return {@code false}.
*
- * @param shuffleMode {@code true} if the shuffle mode is on, {@code false} otherwise.
+ * @param enabled {@code true} to enable the shuffle mode, {@code false} to disable.
*/
- public void setShuffleMode(boolean shuffleMode) {
+ public void setShuffleModeEnabled(boolean enabled) {
try {
- mBinder.setShuffleMode(shuffleMode);
+ mBinder.setShuffleModeEnabled(enabled);
} catch (RemoteException e) {
- Log.e(TAG, "Error in setShuffleMode.", e);
+ Log.e(TAG, "Error in setShuffleModeEnabled.", e);
}
}
@@ -637,8 +637,8 @@
postToCallback(CallbackMessageHandler.MSG_REPEAT_MODE, repeatMode);
}
- private void dispatchShuffleMode(boolean shuffleMode) {
- postToCallback(CallbackMessageHandler.MSG_SHUFFLE_MODE, shuffleMode);
+ private void dispatchShuffleMode(boolean enabled) {
+ postToCallback(CallbackMessageHandler.MSG_SHUFFLE_MODE, enabled);
}
private void dispatchCustomAction(String action, Bundle args) {
@@ -1024,13 +1024,13 @@
/**
* Override to handle the setting of the shuffle mode.
* <p>
- * You should call {@link #setShuffleMode} before end of this method in order to notify
- * the change to the {@link MediaController}, or {@link MediaController#getShuffleMode}
- * could return an invalid value.
+ * You should call {@link #setShuffleModeEnabled} before the end of this method in order to
+ * notify the change to the {@link MediaController}, or
+ * {@link MediaController#isShuffleModeEnabled} could return an invalid value.
*
- * @param shuffleMode true if the shuffle mode is on, false otherwise.
+ * @param enabled true when the shuffle mode is enabled, false otherwise.
*/
- public void onSetShuffleMode(boolean shuffleMode) {
+ public void onSetShuffleModeEnabled(boolean enabled) {
}
/**
@@ -1223,10 +1223,10 @@
}
@Override
- public void onShuffleMode(boolean shuffleMode) {
+ public void onShuffleMode(boolean enabled) {
MediaSession session = mMediaSession.get();
if (session != null) {
- session.dispatchShuffleMode(shuffleMode);
+ session.dispatchShuffleMode(enabled);
}
}
@@ -1468,7 +1468,7 @@
mCallback.onSetRepeatMode((int) msg.obj);
break;
case MSG_SHUFFLE_MODE:
- mCallback.onSetShuffleMode((boolean) msg.obj);
+ mCallback.onSetShuffleModeEnabled((boolean) msg.obj);
break;
case MSG_CUSTOM_ACTION:
mCallback.onCustomAction((String) msg.obj, msg.getData());
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 5cdd201..1ea6109 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -46,7 +46,7 @@
ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI, ACTION_PREPARE,
ACTION_PREPARE_FROM_MEDIA_ID, ACTION_PREPARE_FROM_SEARCH, ACTION_PREPARE_FROM_URI,
- ACTION_SET_REPEAT_MODE, ACTION_SET_SHUFFLE_MODE})
+ ACTION_SET_REPEAT_MODE, ACTION_SET_SHUFFLE_MODE_ENABLED})
@Retention(RetentionPolicy.SOURCE)
public @interface Actions {}
@@ -184,11 +184,11 @@
public static final long ACTION_SET_REPEAT_MODE = 1 << 18;
/**
- * Indicates this session supports the set shuffle mode command.
+ * Indicates this session supports the set shuffle mode enabled command.
*
* @see Builder#setActions(long)
*/
- public static final long ACTION_SET_SHUFFLE_MODE = 1 << 19;
+ public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 1 << 19;
/**
* @hide
@@ -467,7 +467,7 @@
* <li> {@link PlaybackState#ACTION_PREPARE_FROM_SEARCH}</li>
* <li> {@link PlaybackState#ACTION_PREPARE_FROM_URI}</li>
* <li> {@link PlaybackState#ACTION_SET_REPEAT_MODE}</li>
- * <li> {@link PlaybackState#ACTION_SET_SHUFFLE_MODE}</li>
+ * <li> {@link PlaybackState#ACTION_SET_SHUFFLE_MODE_ENABLED}</li>
* </ul>
*/
@Actions
@@ -1003,7 +1003,7 @@
* <li> {@link PlaybackState#ACTION_PREPARE_FROM_SEARCH}</li>
* <li> {@link PlaybackState#ACTION_PREPARE_FROM_URI}</li>
* <li> {@link PlaybackState#ACTION_SET_REPEAT_MODE}</li>
- * <li> {@link PlaybackState#ACTION_SET_SHUFFLE_MODE}</li>
+ * <li> {@link PlaybackState#ACTION_SET_SHUFFLE_MODE_ENABLED}</li>
* </ul>
*
* @param actions The set of actions allowed.
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index 8184f94..39c1554 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -37,7 +37,7 @@
size_t mPosition;
public:
- AssetStream(SkStream* stream);
+ explicit AssetStream(SkStream* stream);
~AssetStream();
// Reads 'length' amount of bytes from 'offset' to 'data'. The 'data' buffer
@@ -60,7 +60,7 @@
const size_t kMinSizeToRead = 8192;
public:
- BufferedStream(SkStream* stream);
+ explicit BufferedStream(SkStream* stream);
~BufferedStream();
// Reads 'length' amount of bytes from 'offset' to 'data'. The 'data' buffer
@@ -79,8 +79,8 @@
size_t mPosition;
public:
- FileStream(const int fd);
- FileStream(const String8 filename);
+ explicit FileStream(const int fd);
+ explicit FileStream(const String8 filename);
~FileStream();
// Reads 'length' amount of bytes from 'offset' to 'data'. The 'data' buffer
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index 75a3b6f..0645543 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -435,14 +435,12 @@
// ----------------------------------------------------------------------------
static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) {
- // ensure that lpVisualizer is deleted before lpJniStorage
- {
+ { //limit scope so that lpVisualizer is deleted before JNI storage data.
sp<Visualizer> lpVisualizer = setVisualizer(env, thiz, 0);
if (lpVisualizer == 0) {
return;
}
}
-
// delete the JNI data
VisualizerJniStorage* lpJniStorage =
(VisualizerJniStorage *)env->GetLongField(thiz, fields.fidJniData);
diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoService.java b/packages/EasterEgg/src/com/android/egg/neko/NekoService.java
index 32e3358..808ec36 100644
--- a/packages/EasterEgg/src/com/android/egg/neko/NekoService.java
+++ b/packages/EasterEgg/src/com/android/egg/neko/NekoService.java
@@ -102,6 +102,14 @@
return false;
}
+ public static void registerJobIfNeeded(Context context, long intervalMinutes) {
+ JobScheduler jss = context.getSystemService(JobScheduler.class);
+ JobInfo info = jss.getPendingJob(JOB_ID);
+ if (info == null) {
+ registerJob(context, intervalMinutes);
+ }
+ }
+
public static void registerJob(Context context, long intervalMinutes) {
JobScheduler jss = context.getSystemService(JobScheduler.class);
jss.cancel(JOB_ID);
diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoTile.java b/packages/EasterEgg/src/com/android/egg/neko/NekoTile.java
index 8a3ec8f..159b40a 100644
--- a/packages/EasterEgg/src/com/android/egg/neko/NekoTile.java
+++ b/packages/EasterEgg/src/com/android/egg/neko/NekoTile.java
@@ -68,6 +68,9 @@
Tile tile = getQsTile();
int foodState = mPrefs.getFoodState();
Food food = new Food(foodState);
+ if (foodState != 0) {
+ NekoService.registerJobIfNeeded(this, food.getInterval(this));
+ }
tile.setIcon(food.getIcon(this));
tile.setLabel(food.getName(this));
tile.setState(foodState != 0 ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
diff --git a/packages/EasterEgg/src/com/android/egg/neko/PrefState.java b/packages/EasterEgg/src/com/android/egg/neko/PrefState.java
index 5f54180..bf71b19 100644
--- a/packages/EasterEgg/src/com/android/egg/neko/PrefState.java
+++ b/packages/EasterEgg/src/com/android/egg/neko/PrefState.java
@@ -43,13 +43,11 @@
public void addCat(Cat cat) {
mPrefs.edit()
.putString(CAT_KEY_PREFIX + String.valueOf(cat.getSeed()), cat.getName())
- .commit();
+ .apply();
}
public void removeCat(Cat cat) {
- mPrefs.edit()
- .remove(CAT_KEY_PREFIX + String.valueOf(cat.getSeed()))
- .commit();
+ mPrefs.edit().remove(CAT_KEY_PREFIX + String.valueOf(cat.getSeed())).apply();
}
public List<Cat> getCats() {
@@ -71,7 +69,7 @@
}
public void setFoodState(int foodState) {
- mPrefs.edit().putInt(FOOD_STATE, foodState).commit();
+ mPrefs.edit().putInt(FOOD_STATE, foodState).apply();
}
public void setListener(PrefsListener listener) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 874eb94..66e1b27 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -111,12 +111,6 @@
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
- /**
- * Milliseconds after unlocking with fingerprint times out, i.e. the user has to use a
- * strong auth method like password, PIN or pattern.
- */
- private static final long FINGERPRINT_UNLOCK_TIMEOUT_MS = 72 * 60 * 60 * 1000;
-
// Callback messages
private static final int MSG_TIME_UPDATE = 301;
private static final int MSG_BATTERY_UPDATE = 302;
@@ -141,6 +135,7 @@
private static final int MSG_SERVICE_STATE_CHANGE = 330;
private static final int MSG_SCREEN_TURNED_ON = 331;
private static final int MSG_SCREEN_TURNED_OFF = 332;
+ private static final int MSG_DREAMING_STATE_CHANGED = 333;
/** Fingerprint state: Not listening to fingerprint. */
private static final int FINGERPRINT_STATE_STOPPED = 0;
@@ -293,6 +288,9 @@
handleScreenTurnedOff();
Trace.endSection();
break;
+ case MSG_DREAMING_STATE_CHANGED:
+ handleDreamingStateChanged(msg.arg1);
+ break;
}
}
};
@@ -604,7 +602,10 @@
}
private void scheduleStrongAuthTimeout() {
- long when = SystemClock.elapsedRealtime() + FINGERPRINT_UNLOCK_TIMEOUT_MS;
+ final DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ long when = SystemClock.elapsedRealtime() + dpm.getRequiredStrongAuthTimeout(null,
+ sCurrentUser);
Intent intent = new Intent(ACTION_STRONG_AUTH_TIMEOUT);
intent.putExtra(USER_ID, sCurrentUser);
PendingIntent sender = PendingIntent.getBroadcast(mContext,
@@ -990,6 +991,17 @@
}
}
+ private void handleDreamingStateChanged(int dreamStart) {
+ final int count = mCallbacks.size();
+ boolean showingDream = dreamStart == 1;
+ for (int i = 0; i < count; i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onDreamingStateChanged(showingDream);
+ }
+ }
+ }
+
/**
* IMPORTANT: Must be called from UI thread.
*/
@@ -1739,6 +1751,14 @@
mHandler.sendEmptyMessage(MSG_SCREEN_TURNED_OFF);
}
+ public void dispatchDreamingStarted() {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DREAMING_STATE_CHANGED, 1, 0));
+ }
+
+ public void dispatchDreamingStopped() {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DREAMING_STATE_CHANGED, 0, 0));
+ }
+
public boolean isDeviceInteractive() {
return mDeviceInteractive;
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 4a2d356..eb29d9b 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -245,4 +245,10 @@
* Called when the state whether we have a lockscreen wallpaper has changed.
*/
public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { }
+
+ /**
+ * Called when the dream's window state is changed.
+ * @param dreaming true if the dream's window has been created and is visible
+ */
+ public void onDreamingStateChanged(boolean dreaming) { }
}
diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk
index 1098a8e..ad07283 100644
--- a/packages/SettingsLib/Android.mk
+++ b/packages/SettingsLib/Android.mk
@@ -19,3 +19,6 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# For the test package.
+include $(call all-makefiles-under, $(LOCAL_PATH))
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java
index 0e3e0d5..5c577f8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java
@@ -126,6 +126,13 @@
* internal storage. Key is {@link UserHandle}.
*/
public SparseLongArray usersSize = new SparseLongArray();
+
+ @Override
+ public String toString() {
+ return "MeasurementDetails: [totalSize: " + totalSize + " availSize: " + availSize
+ + " cacheSize: " + cacheSize + " mediaSize: " + mediaSize
+ + " miscSize: " + miscSize + "usersSize: " + usersSize + "]";
+ }
}
public interface MeasurementReceiver {
@@ -435,7 +442,7 @@
private static long getDirectorySize(IMediaContainerService imcs, File path) {
try {
final long size = imcs.calculateDirectorySize(path.toString());
- Log.d(TAG, "getDirectorySize(" + path + ") returned " + size);
+ if (LOGV) Log.v(TAG, "getDirectorySize(" + path + ") returned " + size);
return size;
} catch (Exception e) {
Log.w(TAG, "Could not read memory from default container service for " + path, e);
diff --git a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
index a99e668..af8fd4c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
@@ -23,6 +23,7 @@
import android.hardware.display.DisplayManager;
import android.os.AsyncTask;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
@@ -207,39 +208,41 @@
/**
* Asynchronously applies display density changes to the specified display.
+ * <p>
+ * The change will be applied to the user specified by the value of
+ * {@link UserHandle#myUserId()} at the time the method is called.
*
* @param displayId the identifier of the display to modify
*/
public static void clearForcedDisplayDensity(final int displayId) {
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- try {
- final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- wm.clearForcedDisplayDensity(displayId);
- } catch (RemoteException exc) {
- Log.w(LOG_TAG, "Unable to clear forced display density setting");
- }
+ final int userId = UserHandle.myUserId();
+ AsyncTask.execute(() -> {
+ try {
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ wm.clearForcedDisplayDensityForUser(displayId, userId);
+ } catch (RemoteException exc) {
+ Log.w(LOG_TAG, "Unable to clear forced display density setting");
}
});
}
/**
* Asynchronously applies display density changes to the specified display.
+ * <p>
+ * The change will be applied to the user specified by the value of
+ * {@link UserHandle#myUserId()} at the time the method is called.
*
* @param displayId the identifier of the display to modify
* @param density the density to force for the specified display
*/
public static void setForcedDisplayDensity(final int displayId, final int density) {
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- try {
- final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- wm.setForcedDisplayDensity(displayId, density);
- } catch (RemoteException exc) {
- Log.w(LOG_TAG, "Unable to save forced display density setting");
- }
+ final int userId = UserHandle.myUserId();
+ AsyncTask.execute(() -> {
+ try {
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ wm.setForcedDisplayDensityForUser(displayId, density, userId);
+ } catch (RemoteException exc) {
+ Log.w(LOG_TAG, "Unable to save forced display density setting");
}
});
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index a50b366..05585e53e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -28,8 +28,12 @@
import android.content.res.TypedArray;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
import android.support.v4.widget.DrawerLayout;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -56,6 +60,7 @@
protected static final boolean DEBUG_TIMING = false;
private static final String TAG = "SettingsDrawerActivity";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final String EXTRA_SHOW_MENU = "show_drawer_menu";
@@ -73,6 +78,7 @@
private FrameLayout mContentHeaderContainer;
private DrawerLayout mDrawerLayout;
private boolean mShowingMenu;
+ private UserManager mUserManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -108,8 +114,10 @@
public void onItemClick(android.widget.AdapterView<?> parent, View view, int position,
long id) {
onTileClicked(mDrawerAdapter.getTile(position));
- };
+ }
});
+
+ mUserManager = UserManager.get(this);
if (DEBUG_TIMING) Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime)
+ " ms");
}
@@ -138,8 +146,16 @@
new CategoriesUpdater().execute();
}
- if (getIntent() != null && getIntent().getBooleanExtra(EXTRA_SHOW_MENU, false)) {
- showMenuIcon();
+ final Intent intent = getIntent();
+ if (intent != null) {
+ if (intent.hasExtra(EXTRA_SHOW_MENU)) {
+ if (intent.getBooleanExtra(EXTRA_SHOW_MENU, false)) {
+ // Intent explicitly set to show menu.
+ showMenuIcon();
+ }
+ } else if (isTopLevelTile(intent)) {
+ showMenuIcon();
+ }
}
}
@@ -152,6 +168,30 @@
super.onPause();
}
+ private boolean isTopLevelTile(Intent intent) {
+ final ComponentName componentName = intent.getComponent();
+ if (componentName == null) {
+ return false;
+ }
+ // Look for a tile that has the same component as incoming intent
+ final List<DashboardCategory> categories = getDashboardCategories();
+ for (DashboardCategory category : categories) {
+ for (Tile tile : category.tiles) {
+ if (TextUtils.equals(tile.intent.getComponent().getClassName(),
+ componentName.getClassName())) {
+ if (DEBUG) {
+ Log.d(TAG, "intent is for top level tile: " + tile.title);
+ }
+ return true;
+ }
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Intent is not for top level settings " + intent);
+ }
+ return false;
+ }
+
public void addCategoryListener(CategoryListener listener) {
mCategoryListeners.add(listener);
}
@@ -257,6 +297,7 @@
return true;
}
try {
+ updateUserHandlesIfNeeded(tile);
int numUserHandles = tile.userHandle.size();
if (numUserHandles > 1) {
ProfileSelectDialog.show(getFragmentManager(), tile);
@@ -278,6 +319,24 @@
return true;
}
+ private void updateUserHandlesIfNeeded(Tile tile) {
+ List<UserHandle> userHandles = tile.userHandle;
+
+ for (int i = userHandles.size() - 1; i >= 0; i--) {
+ if (mUserManager.getUserInfo(userHandles.get(i).getIdentifier()) == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Delete the user: " + userHandles.get(i).getIdentifier());
+ }
+ userHandles.remove(i);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public void setUserManager(UserManager userManager) {
+ mUserManager = userManager;
+ }
+
protected void onTileClicked(Tile tile) {
if (openTile(tile)) {
finish();
diff --git a/packages/SettingsLib/tests/Android.mk b/packages/SettingsLib/tests/Android.mk
index d3ffffa..4208522 100644
--- a/packages/SettingsLib/tests/Android.mk
+++ b/packages/SettingsLib/tests/Android.mk
@@ -16,6 +16,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := platform
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -23,7 +24,10 @@
LOCAL_PACKAGE_NAME := SettingsLibTests
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ espresso-core \
+ mockito-target-minus-junit4
include frameworks/base/packages/SettingsLib/common.mk
diff --git a/packages/SettingsLib/tests/AndroidManifest.xml b/packages/SettingsLib/tests/AndroidManifest.xml
index 00d16fc..e6d133b 100644
--- a/packages/SettingsLib/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/AndroidManifest.xml
@@ -17,11 +17,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib">
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.settingslib"
android:label="Tests for SettingsLib">
</instrumentation>
diff --git a/packages/SettingsLib/tests/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java b/packages/SettingsLib/tests/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
new file mode 100644
index 0000000..4d7d4cf
--- /dev/null
+++ b/packages/SettingsLib/tests/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib.drawer;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.settingslib.drawer.SettingsDrawerActivity;
+import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SettingsDrawerActivityTest {
+ @Mock
+ private UserManager mUserManager;
+
+ @Rule
+ public ActivityTestRule<TestActivity> mActivityRule =
+ new ActivityTestRule<>(TestActivity.class, true, true);
+
+ private static final UserHandle NORMAL_USER = UserHandle.of(1111);
+ private static final UserHandle REMOVED_USER = UserHandle.of(2222);
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ final UserInfo userInfo = new UserInfo(
+ NORMAL_USER.getIdentifier(), "test_user", UserInfo.FLAG_RESTRICTED);
+ when(mUserManager.getUserInfo(NORMAL_USER.getIdentifier())).thenReturn(userInfo);
+ }
+
+ @Test
+ public void testUpdateUserHandlesIfNeeded_Normal() {
+ TestActivity activity = mActivityRule.getActivity();
+ activity.setUserManager(mUserManager);
+
+ Tile tile = new Tile();
+ tile.intent = new Intent();
+ tile.userHandle.add(NORMAL_USER);
+
+ activity.openTile(tile);
+
+ assertEquals(tile.userHandle.size(), 1);
+ assertEquals(tile.userHandle.get(0).getIdentifier(), NORMAL_USER.getIdentifier());
+ verify(mUserManager, times(1)).getUserInfo(NORMAL_USER.getIdentifier());
+ }
+
+ @Test
+ public void testUpdateUserHandlesIfNeeded_Remove() {
+ TestActivity activity = mActivityRule.getActivity();
+ activity.setUserManager(mUserManager);
+
+ Tile tile = new Tile();
+ tile.intent = new Intent();
+ tile.userHandle.add(REMOVED_USER);
+ tile.userHandle.add(NORMAL_USER);
+ tile.userHandle.add(REMOVED_USER);
+
+ activity.openTile(tile);
+
+ assertEquals(tile.userHandle.size(), 1);
+ assertEquals(tile.userHandle.get(0).getIdentifier(), NORMAL_USER.getIdentifier());
+ verify(mUserManager, times(1)).getUserInfo(NORMAL_USER.getIdentifier());
+ verify(mUserManager, times(2)).getUserInfo(REMOVED_USER.getIdentifier());
+ }
+
+ @Test
+ public void startActivityWithNoExtra_showNoHamburgerMenu() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.startActivitySync(new Intent(instrumentation.getTargetContext(),
+ TestActivity.class));
+
+ onView(withContentDescription(R.string.content_description_menu_button))
+ .check(doesNotExist());
+ }
+
+ @Test
+ public void startActivityWithExtraToHideMenu_showNoHamburgerMenu() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
+ .putExtra(TestActivity.EXTRA_SHOW_MENU, false);
+ instrumentation.startActivitySync(intent);
+
+ onView(withContentDescription(R.string.content_description_menu_button))
+ .check(doesNotExist());
+ }
+
+ @Test
+ public void startActivityWithExtraToShowMenu_showHamburgerMenu() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
+ .putExtra(TestActivity.EXTRA_SHOW_MENU, true);
+ instrumentation.startActivitySync(intent);
+
+ onView(withContentDescription(R.string.content_description_menu_button))
+ .check(matches(isDisplayed()));
+ }
+
+ /**
+ * Test Activity in this test.
+ *
+ * Use this activity because SettingsDrawerActivity hasn't been registered in its
+ * AndroidManifest.xml
+ */
+ public static class TestActivity extends SettingsDrawerActivity {}
+}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index c1e4e2a..672f88d 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -36,7 +36,7 @@
<fraction name="def_window_transition_scale">100%</fraction>
<bool name="def_haptic_feedback">true</bool>
- <bool name="def_bluetooth_on">false</bool>
+ <bool name="def_bluetooth_on">true</bool>
<bool name="def_wifi_display_on">false</bool>
<bool name="def_install_non_market_apps">false</bool>
<bool name="def_package_verifier_enable">true</bool>
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 258c82e..71bfe85 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -23,6 +23,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
LOCAL_STATIC_ANDROID_LIBRARIES := \
+ SystemUIPluginLib \
Keyguard \
android-support-v7-recyclerview \
android-support-v7-preference \
@@ -43,9 +44,11 @@
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-ifneq ($(SYSTEM_UI_INCREMENTAL_BUILDS),)
+ifneq ($(INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_JACK_ENABLED := incremental
+ LOCAL_DX_FLAGS := --multi-dex
+ LOCAL_JACK_FLAGS := --multi-dex native
endif
include frameworks/base/packages/SettingsLib/common.mk
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3cc16de..8ed1be5 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -138,6 +138,9 @@
android:protectionLevel="signature" />
<uses-permission android:name="com.android.systemui.permission.SELF" />
+ <permission android:name="com.android.systemui.permission.PLUGIN"
+ android:protectionLevel="signature" />
+
<!-- Adding Quick Settings tiles -->
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
diff --git a/packages/SystemUI/plugin/Android.mk b/packages/SystemUI/plugin/Android.mk
new file mode 100644
index 0000000..86527db
--- /dev/null
+++ b/packages/SystemUI/plugin/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := SystemUIPluginLib
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAR_EXCLUDE_FILES := none
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/core/res/res/color/watch_switch_track_color_material.xml b/packages/SystemUI/plugin/AndroidManifest.xml
similarity index 61%
copy from core/res/res/color/watch_switch_track_color_material.xml
copy to packages/SystemUI/plugin/AndroidManifest.xml
index 402a536..7c057dc 100644
--- a/core/res/res/color/watch_switch_track_color_material.xml
+++ b/packages/SystemUI/plugin/AndroidManifest.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!--
+ Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,9 +14,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- Used for the background of switch track for watch switch preference. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false"
- android:alpha="0.4" android:color="?attr/colorPrimary" />
- <item android:color="?attr/colorPrimary" />
-</selector>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.plugins">
+
+ <uses-sdk
+ android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SystemUI/plugin/ExamplePlugin/Android.mk b/packages/SystemUI/plugin/ExamplePlugin/Android.mk
new file mode 100644
index 0000000..4c82c75
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_PACKAGE_NAME := ExamplePlugin
+
+LOCAL_JAVA_LIBRARIES := SystemUIPluginLib
+
+LOCAL_CERTIFICATE := platform
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/plugin/ExamplePlugin/AndroidManifest.xml b/packages/SystemUI/plugin/ExamplePlugin/AndroidManifest.xml
new file mode 100644
index 0000000..bd2c71c
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.systemui.plugin.testoverlayplugin">
+
+ <uses-permission android:name="com.android.systemui.permission.PLUGIN" />
+
+ <application>
+ <service android:name=".SampleOverlayPlugin">
+ <intent-filter>
+ <action android:name="com.android.systemui.action.PLUGIN_OVERLAY" />
+ </intent-filter>
+ </service>
+ </application>
+
+</manifest>
diff --git a/packages/SystemUI/plugin/ExamplePlugin/res/layout/colored_overlay.xml b/packages/SystemUI/plugin/ExamplePlugin/res/layout/colored_overlay.xml
new file mode 100644
index 0000000..b2910cb
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/res/layout/colored_overlay.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+-->
+
+<com.android.systemui.plugin.testoverlayplugin.CustomView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#80ff0000" />
diff --git a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java
new file mode 100644
index 0000000..5fdbbf9
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugin.testoverlayplugin;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * View with some logging to show that its being run.
+ */
+public class CustomView extends View {
+
+ private static final String TAG = "CustomView";
+
+ public CustomView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ Log.d(TAG, "new instance");
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ Log.d(TAG, "onAttachedToWindow");
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ Log.d(TAG, "onDetachedFromWindow");
+ }
+}
diff --git a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java
new file mode 100644
index 0000000..a2f84dc
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugin.testoverlayplugin;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.OverlayPlugin;
+
+public class SampleOverlayPlugin implements OverlayPlugin {
+ private static final String TAG = "SampleOverlayPlugin";
+ private Context mPluginContext;
+
+ private View mStatusBarView;
+ private View mNavBarView;
+
+ @Override
+ public int getVersion() {
+ Log.d(TAG, "getVersion " + VERSION);
+ return VERSION;
+ }
+
+ @Override
+ public void onCreate(Context sysuiContext, Context pluginContext) {
+ Log.d(TAG, "onCreate");
+ mPluginContext = pluginContext;
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy");
+ if (mStatusBarView != null) {
+ mStatusBarView.post(
+ () -> ((ViewGroup) mStatusBarView.getParent()).removeView(mStatusBarView));
+ }
+ if (mNavBarView != null) {
+ mNavBarView.post(() -> ((ViewGroup) mNavBarView.getParent()).removeView(mNavBarView));
+ }
+ }
+
+ @Override
+ public void setup(View statusBar, View navBar) {
+ Log.d(TAG, "Setup");
+
+ if (statusBar instanceof ViewGroup) {
+ mStatusBarView = LayoutInflater.from(mPluginContext)
+ .inflate(R.layout.colored_overlay, (ViewGroup) statusBar, false);
+ ((ViewGroup) statusBar).addView(mStatusBarView);
+ }
+ if (navBar instanceof ViewGroup) {
+ mNavBarView = LayoutInflater.from(mPluginContext)
+ .inflate(R.layout.colored_overlay, (ViewGroup) navBar, false);
+ ((ViewGroup) navBar).addView(mNavBarView);
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
new file mode 100644
index 0000000..91a2604
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins;
+
+import android.view.View;
+
+public interface OverlayPlugin extends Plugin {
+
+ String ACTION = "com.android.systemui.action.PLUGIN_OVERLAY";
+ int VERSION = 1;
+
+ void setup(View statusBar, View navBar);
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
new file mode 100644
index 0000000..b31b199
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins;
+
+import android.content.Context;
+
+/**
+ * Plugins are separate APKs that
+ * are expected to implement interfaces provided by SystemUI. Their
+ * code is dynamically loaded into the SysUI process which can allow
+ * for multiple prototypes to be created and run on a single android
+ * build.
+ *
+ * PluginLifecycle:
+ * <pre class="prettyprint">
+ *
+ * plugin.onCreate(Context sysuiContext, Context pluginContext);
+ * --- This is always called before any other calls
+ *
+ * pluginListener.onPluginConnected(Plugin p);
+ * --- This lets the plugin hook know that a plugin is now connected.
+ *
+ * ** Any other calls back and forth between sysui/plugin **
+ *
+ * pluginListener.onPluginDisconnected(Plugin p);
+ * --- Lets the plugin hook know that it should stop interacting with
+ * this plugin and drop all references to it.
+ *
+ * plugin.onDestroy();
+ * --- Finally the plugin can perform any cleanup to ensure that its not
+ * leaking into the SysUI process.
+ *
+ * Any time a plugin APK is updated the plugin is destroyed and recreated
+ * to load the new code/resources.
+ *
+ * </pre>
+ *
+ * Creating plugin hooks:
+ *
+ * To create a plugin hook, first create an interface in
+ * frameworks/base/packages/SystemUI/plugin that extends Plugin.
+ * Include in it any hooks you want to be able to call into from
+ * sysui and create callback interfaces for anything you need to
+ * pass through into the plugin.
+ *
+ * Then to attach to any plugins simply add a plugin listener and
+ * onPluginConnected will get called whenever new plugins are installed,
+ * updated, or enabled. Like this example from SystemUIApplication:
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * PluginManager.getInstance(this).addPluginListener(OverlayPlugin.COMPONENT,
+ * new PluginListener<OverlayPlugin>() {
+ * @Override
+ * public void onPluginConnected(OverlayPlugin plugin) {
+ * PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
+ * if (phoneStatusBar != null) {
+ * plugin.setup(phoneStatusBar.getStatusBarWindow(),
+ * phoneStatusBar.getNavigationBarView());
+ * }
+ * }
+ * }, OverlayPlugin.VERSION, true /* Allow multiple plugins *\/);
+ * }
+ * </pre>
+ * Note the VERSION included here. Any time incompatible changes in the
+ * interface are made, this version should be changed to ensure old plugins
+ * aren't accidentally loaded. Since the plugin library is provided by
+ * SystemUI, default implementations can be added for new methods to avoid
+ * version changes when possible.
+ *
+ * Implementing a Plugin:
+ *
+ * See the ExamplePlugin for an example Android.mk on how to compile
+ * a plugin. Note that SystemUILib is not static for plugins, its classes
+ * are provided by SystemUI.
+ *
+ * Plugin security is based around a signature permission, so plugins must
+ * hold the following permission in their manifest.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <uses-permission android:name="com.android.systemui.permission.PLUGIN" />
+ * }
+ * </pre>
+ *
+ * A plugin is found through a querying for services, so to let SysUI know
+ * about it, create a service with a name that points at your implementation
+ * of the plugin interface with the action accompanying it:
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <service android:name=".TestOverlayPlugin">
+ * <intent-filter>
+ * <action android:name="com.android.systemui.action.PLUGIN_COMPONENT" />
+ * </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ */
+public interface Plugin {
+
+ /**
+ * Should be implemented as the following directly referencing the version constant
+ * from the plugin interface being implemented, this will allow recompiles to automatically
+ * pick up the current version.
+ * <pre class="prettyprint">
+ * {@literal
+ * public int getVersion() {
+ * return VERSION;
+ * }
+ * }
+ * @return
+ */
+ int getVersion();
+
+ default void onCreate(Context sysuiContext, Context pluginContext) {
+ }
+
+ default void onDestroy() {
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
new file mode 100644
index 0000000..2a7139c
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.LayoutInflater;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.system.PathClassLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver {
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = "PluginInstanceManager";
+ private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
+
+ private final Context mContext;
+ private final PluginListener<T> mListener;
+ private final String mAction;
+ private final boolean mAllowMultiple;
+ private final int mVersion;
+
+ @VisibleForTesting
+ final MainHandler mMainHandler;
+ @VisibleForTesting
+ final PluginHandler mPluginHandler;
+ private final boolean isDebuggable;
+ private final PackageManager mPm;
+ private final ClassLoaderFactory mClassLoaderFactory;
+
+ PluginInstanceManager(Context context, String action, PluginListener<T> listener,
+ boolean allowMultiple, Looper looper, int version) {
+ this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version,
+ Build.IS_DEBUGGABLE, new ClassLoaderFactory());
+ }
+
+ @VisibleForTesting
+ PluginInstanceManager(Context context, PackageManager pm, String action,
+ PluginListener<T> listener, boolean allowMultiple, Looper looper, int version,
+ boolean debuggable, ClassLoaderFactory classLoaderFactory) {
+ mMainHandler = new MainHandler(Looper.getMainLooper());
+ mPluginHandler = new PluginHandler(looper);
+ mContext = context;
+ mPm = pm;
+ mAction = action;
+ mListener = listener;
+ mAllowMultiple = allowMultiple;
+ mVersion = version;
+ isDebuggable = debuggable;
+ mClassLoaderFactory = classLoaderFactory;
+ }
+
+ public void startListening() {
+ if (DEBUG) Log.d(TAG, "startListening");
+ mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL);
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(this, filter);
+ filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
+ mContext.registerReceiver(this, filter);
+ }
+
+ public void stopListening() {
+ if (DEBUG) Log.d(TAG, "stopListening");
+ ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
+ for (PluginInfo plugin : plugins) {
+ mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED,
+ plugin.mPlugin).sendToTarget();
+ }
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Log.d(TAG, "onReceive " + intent);
+ if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+ mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL);
+ } else {
+ Uri data = intent.getData();
+ String pkgName = data.getEncodedSchemeSpecificPart();
+ mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkgName).sendToTarget();
+ if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
+ mPluginHandler.obtainMessage(PluginHandler.QUERY_PKG, pkgName).sendToTarget();
+ }
+ }
+ }
+
+ public boolean checkAndDisable(String className) {
+ boolean disableAny = false;
+ ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
+ for (PluginInfo info : plugins) {
+ if (className.startsWith(info.mPackage)) {
+ disable(info);
+ disableAny = true;
+ }
+ }
+ return disableAny;
+ }
+
+ public void disableAll() {
+ ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
+ plugins.forEach(this::disable);
+ }
+
+ private void disable(PluginInfo info) {
+ // Live by the sword, die by the sword.
+ // Misbehaving plugins get disabled and won't come back until uninstall/reinstall.
+
+ // If a plugin is detected in the stack of a crash then this will be called for that
+ // plugin, if the plugin causing a crash cannot be identified, they are all disabled
+ // assuming one of them must be bad.
+ Log.w(TAG, "Disabling plugin " + info.mPackage + "/" + info.mClass);
+ mPm.setComponentEnabledSetting(
+ new ComponentName(info.mPackage, info.mClass),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ private class MainHandler extends Handler {
+ private static final int PLUGIN_CONNECTED = 1;
+ private static final int PLUGIN_DISCONNECTED = 2;
+
+ public MainHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case PLUGIN_CONNECTED:
+ if (DEBUG) Log.d(TAG, "onPluginConnected");
+ PluginInfo<T> info = (PluginInfo<T>) msg.obj;
+ info.mPlugin.onCreate(mContext, info.mPluginContext);
+ mListener.onPluginConnected(info.mPlugin);
+ break;
+ case PLUGIN_DISCONNECTED:
+ if (DEBUG) Log.d(TAG, "onPluginDisconnected");
+ mListener.onPluginDisconnected((T) msg.obj);
+ ((T) msg.obj).onDestroy();
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+ }
+
+ static class ClassLoaderFactory {
+ public ClassLoader createClassLoader(String path, ClassLoader base) {
+ return new PathClassLoader(path, base);
+ }
+ }
+
+ private class PluginHandler extends Handler {
+ private static final int QUERY_ALL = 1;
+ private static final int QUERY_PKG = 2;
+ private static final int REMOVE_PKG = 3;
+
+ private final ArrayList<PluginInfo<T>> mPlugins = new ArrayList<>();
+
+ public PluginHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case QUERY_ALL:
+ if (DEBUG) Log.d(TAG, "queryAll " + mAction);
+ for (int i = mPlugins.size() - 1; i >= 0; i--) {
+ PluginInfo<T> plugin = mPlugins.get(i);
+ mListener.onPluginDisconnected(plugin.mPlugin);
+ plugin.mPlugin.onDestroy();
+ }
+ mPlugins.clear();
+ handleQueryPlugins(null);
+ break;
+ case REMOVE_PKG:
+ String pkg = (String) msg.obj;
+ for (int i = mPlugins.size() - 1; i >= 0; i--) {
+ final PluginInfo<T> plugin = mPlugins.get(i);
+ if (plugin.mPackage.equals(pkg)) {
+ mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED,
+ plugin.mPlugin).sendToTarget();
+ mPlugins.remove(i);
+ }
+ }
+ break;
+ case QUERY_PKG:
+ String p = (String) msg.obj;
+ if (DEBUG) Log.d(TAG, "queryPkg " + p + " " + mAction);
+ if (mAllowMultiple || (mPlugins.size() == 0)) {
+ handleQueryPlugins(p);
+ } else {
+ if (DEBUG) Log.d(TAG, "Too many of " + mAction);
+ }
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ private void handleQueryPlugins(String pkgName) {
+ // This isn't actually a service and shouldn't ever be started, but is
+ // a convenient PM based way to manage our plugins.
+ Intent intent = new Intent(mAction);
+ if (pkgName != null) {
+ intent.setPackage(pkgName);
+ }
+ List<ResolveInfo> result =
+ mPm.queryIntentServices(intent, 0);
+ if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins");
+ if (result.size() > 1 && !mAllowMultiple) {
+ // TODO: Show warning.
+ Log.w(TAG, "Multiple plugins found for " + mAction);
+ return;
+ }
+ for (ResolveInfo info : result) {
+ ComponentName name = new ComponentName(info.serviceInfo.packageName,
+ info.serviceInfo.name);
+ PluginInfo<T> t = handleLoadPlugin(name);
+ if (t == null) continue;
+ mMainHandler.obtainMessage(mMainHandler.PLUGIN_CONNECTED, t).sendToTarget();
+ mPlugins.add(t);
+ }
+ }
+
+ protected PluginInfo<T> handleLoadPlugin(ComponentName component) {
+ // This was already checked, but do it again here to make extra extra sure, we don't
+ // use these on production builds.
+ if (!isDebuggable) {
+ // Never ever ever allow these on production builds, they are only for prototyping.
+ Log.d(TAG, "Somehow hit second debuggable check");
+ return null;
+ }
+ String pkg = component.getPackageName();
+ String cls = component.getClassName();
+ try {
+ PackageManager pm = mPm;
+ ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
+ // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on
+ if (pm.checkPermission(PLUGIN_PERMISSION, pkg)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "Plugin doesn't have permission: " + pkg);
+ return null;
+ }
+ // Create our own ClassLoader so we can use our own code as the parent.
+ ClassLoader classLoader = mClassLoaderFactory.createClassLoader(info.sourceDir,
+ getClass().getClassLoader());
+ Context pluginContext = new PluginContextWrapper(
+ mContext.createApplicationContext(info, 0), classLoader);
+ Class<?> pluginClass = Class.forName(cls, true, classLoader);
+ T plugin = (T) pluginClass.newInstance();
+ if (plugin.getVersion() != mVersion) {
+ // TODO: Warn user.
+ Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion()
+ + ", expected " + mVersion);
+ return null;
+ }
+ if (DEBUG) Log.d(TAG, "createPlugin");
+ return new PluginInfo(pkg, cls, plugin, pluginContext);
+ } catch (Exception e) {
+ Log.w(TAG, "Couldn't load plugin: " + pkg, e);
+ return null;
+ }
+ }
+ }
+
+ public static class PluginContextWrapper extends ContextWrapper {
+ private final ClassLoader mClassLoader;
+ private LayoutInflater mInflater;
+
+ public PluginContextWrapper(Context base, ClassLoader classLoader) {
+ super(base);
+ mClassLoader = classLoader;
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return mClassLoader;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (LAYOUT_INFLATER_SERVICE.equals(name)) {
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
+ }
+ return mInflater;
+ }
+ return getBaseContext().getSystemService(name);
+ }
+ }
+
+ private static class PluginInfo<T> {
+ private final Context mPluginContext;
+ private T mPlugin;
+ private String mClass;
+ private String mPackage;
+
+ public PluginInfo(String pkg, String cls, T plugin, Context pluginContext) {
+ mPlugin = plugin;
+ mClass = cls;
+ mPackage = pkg;
+ mPluginContext = pluginContext;
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
new file mode 100644
index 0000000..b2f92d6
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+/**
+ * Interface for listening to plugins being connected.
+ */
+public interface PluginListener<T extends Plugin> {
+ /**
+ * Called when the plugin has been loaded and is ready to be used.
+ * This may be called multiple times if multiple plugins are allowed.
+ * It may also be called in the future if the plugin package changes
+ * and needs to be reloaded.
+ */
+ void onPluginConnected(T plugin);
+
+ /**
+ * Called when a plugin has been uninstalled/updated and should be removed
+ * from use.
+ */
+ default void onPluginDisconnected(T plugin) {
+ // Optional.
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
new file mode 100644
index 0000000..aa0b3c5
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+
+/**
+ * @see Plugin
+ */
+public class PluginManager {
+
+ private static PluginManager sInstance;
+
+ private final HandlerThread mBackgroundThread;
+ private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap
+ = new ArrayMap<>();
+ private final Context mContext;
+ private final PluginInstanceManagerFactory mFactory;
+ private final boolean isDebuggable;
+
+ private PluginManager(Context context) {
+ this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE,
+ Thread.getDefaultUncaughtExceptionHandler());
+ }
+
+ @VisibleForTesting
+ PluginManager(Context context, PluginInstanceManagerFactory factory, boolean debuggable,
+ UncaughtExceptionHandler defaultHandler) {
+ mContext = context;
+ mFactory = factory;
+ mBackgroundThread = new HandlerThread("Plugins");
+ mBackgroundThread.start();
+ isDebuggable = debuggable;
+
+ PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
+ defaultHandler);
+ Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
+ }
+
+ public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+ int version) {
+ addPluginListener(action, listener, version, false);
+ }
+
+ public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+ int version, boolean allowMultiple) {
+ if (!isDebuggable) {
+ // Never ever ever allow these on production builds, they are only for prototyping.
+ return;
+ }
+ PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
+ allowMultiple, mBackgroundThread.getLooper(), version);
+ p.startListening();
+ mPluginMap.put(listener, p);
+ }
+
+ public void removePluginListener(PluginListener<?> listener) {
+ if (!isDebuggable) {
+ // Never ever ever allow these on production builds, they are only for prototyping.
+ return;
+ }
+ if (!mPluginMap.containsKey(listener)) return;
+ mPluginMap.remove(listener).stopListening();
+ }
+
+ public static PluginManager getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new PluginManager(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+
+ @VisibleForTesting
+ public static class PluginInstanceManagerFactory {
+ public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
+ String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
+ int version) {
+ return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
+ version);
+ }
+ }
+
+ private class PluginExceptionHandler implements UncaughtExceptionHandler {
+ private final UncaughtExceptionHandler mHandler;
+
+ private PluginExceptionHandler(UncaughtExceptionHandler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable throwable) {
+ // Search for and disable plugins that may have been involved in this crash.
+ boolean disabledAny = checkStack(throwable);
+ if (!disabledAny) {
+ // We couldn't find any plugins involved in this crash, just to be safe
+ // disable all the plugins, so we can be sure that SysUI is running as
+ // best as possible.
+ for (PluginInstanceManager manager : mPluginMap.values()) {
+ manager.disableAll();
+ }
+ }
+
+ // Run the normal exception handler so we can crash and cleanup our state.
+ mHandler.uncaughtException(thread, throwable);
+ }
+
+ private boolean checkStack(Throwable throwable) {
+ if (throwable == null) return false;
+ boolean disabledAny = false;
+ for (StackTraceElement element : throwable.getStackTrace()) {
+ for (PluginInstanceManager manager : mPluginMap.values()) {
+ disabledAny |= manager.checkAndDisable(element.getClassName());
+ }
+ }
+ return disabledAny | checkStack(throwable.getCause());
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginUtils.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginUtils.java
new file mode 100644
index 0000000..af49d43
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginUtils.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+
+public class PluginUtils {
+
+ public static void setId(Context sysuiContext, View view, String id) {
+ int i = sysuiContext.getResources().getIdentifier(id, "id", sysuiContext.getPackageName());
+ view.setId(i);
+ }
+}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 9182f7e..364885a 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -37,3 +37,6 @@
-keep class ** extends android.support.v14.preference.PreferenceFragment
-keep class com.android.systemui.tuner.*
+-keep class com.android.systemui.plugins.** {
+ public protected **;
+}
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_0.xml b/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
index f63dfb12..b78d3bf 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,17 +15,15 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="32dp"
- android:height="32dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:pathData="M17.700001,8.000000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
- android:fillColor="#4DFFFFFF"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_1.xml b/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
index 7fb423e..e055de7 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,20 +15,18 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="32dp"
- android:height="32dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.7,20.0l2.0,0.0l0.0,2.0l-2.0,0.0z"/>
+ android:pathData="M10.0,14.6l-8.0,8.0l8.0,0.0l0,-8z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.7,10.0l2.0,0.0l0.0,8.1l-2.0,0.0z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#4DFFFFFF"
- android:pathData="M17.7,8.0l4.299999,0.0 0.0,-6.0 -20.0,20.0 15.700001,0.0z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M10.1,13.9l-8.1,8.1 8.1,0.0z"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_2.xml b/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
index 3358d65..8a48817 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,20 +15,18 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="32dp"
- android:height="32dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.0,10.6l-12.0,12.0l12.0,0.0L14.0,10.6z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.900000,10.000000l-11.900000,12.000000 11.900000,0.000000z"/>
- <path
- android:pathData="M17.700001,8.000000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
- android:fillColor="#4DFFFFFF"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_3.xml b/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
index 63838a9..39cc94c 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,20 +15,18 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="32dp"
- android:height="32dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,19.900000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,9.900000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M16.700001,7.200000l-14.700001,14.700000 14.700001,0.000000z"/>
- <path
- android:pathData="M17.700001,7.900000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
- android:fillColor="#4DFFFFFF"/>
+ android:pathData="M14.1,14.1l2.9,0.0 0.0,-6.5 -15.0,15.0 12.1,0.0z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
index 76690cc..012e95e 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,17 +15,14 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="32dp"
- android:height="32dp"
+ android:width="32.0dp"
+ android:height="32.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M2.000000,22.000000l15.700001,0.000000 0.000000,-14.000000 4.299999,0.000000 0.000000,-6.000000z"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
index 50c427e..e6f9292 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -14,17 +14,15 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.200000,-1.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
index a2d11a0..d423ccb 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -14,20 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,13.2c-0.1,0.0 -0.3,-0.1 -0.4,-0.1c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,0.0 -0.6,-0.1 -0.9,-0.1c0.0,0.0 0.0,0.0 -0.1,0.0c0.0,0.0 0.0,0.0 0.0,0.0s0.0,0.0 0.0,0.0c0.0,0.0 0.0,0.0 -0.1,0.0c-0.3,0.0 -0.6,0.0 -0.9,0.1c-0.1,0.0 -0.3,0.0 -0.4,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.1,0.0 -0.1,0.0 -0.2,0.1c-1.6,0.5 -2.7,1.3 -2.8,1.5l5.3,6.6l0.0,0.0l0.0,0.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.700002,13.2z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.000000,22.000000l5.500000,-6.800000c-0.200000,-0.200000 -2.300000,-1.900000 -5.500000,-1.900000s-5.300000,1.800000 -5.500000,1.900000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
index f2043fc..1982130 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -14,20 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l4.9,0.0c-1.0,-0.7 -3.4,-2.2 -6.7,-2.2c-4.1,0.0 -6.9,2.2 -7.2,2.5l7.2,9.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.800001,12.2z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.000000,11.600000c-1.300000,-0.700000 -3.400000,-1.600000 -6.000000,-1.600000c-4.400000,0.000000 -7.300000,2.400000 -7.600000,2.700000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,11.600000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.800001,1.9 -1.800001,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
index b7a4f4c..b350111 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -14,20 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#4DFFFFFF"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0l1.0,-1.2C20.0,10.6 16.8,8.0 12.0,8.0s-8.0,2.6 -8.5,3.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.000000,8.600000c-1.600000,-0.700000 -3.600000,-1.300000 -6.000000,-1.300000c-5.300000,0.000000 -8.900000,3.000000 -9.200000,3.200000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.600000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
index 35a9138..136a004 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -14,17 +14,14 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="#FFFFFF"/>
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_0.xml b/packages/SystemUI/res/drawable/stat_sys_signal_0.xml
index 643c4f9..8bc872a 100644
--- a/packages/SystemUI/res/drawable/stat_sys_signal_0.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_0.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,17 +15,14 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="17dp"
- android:height="17dp"
+ android:width="17.0dp"
+ android:height="17.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:pathData="M17.700001,8.000000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillColor="?attr/backgroundColor"/>
+ <path
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_1.xml b/packages/SystemUI/res/drawable/stat_sys_signal_1.xml
index 64781c3..8fa7630 100644
--- a/packages/SystemUI/res/drawable/stat_sys_signal_1.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_1.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,20 +15,17 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="17dp"
- android:height="17dp"
+ android:width="17.0dp"
+ android:height="17.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.7,20.0l2.0,0.0l0.0,2.0l-2.0,0.0z"/>
+ android:pathData="M10.0,14.6l-8.0,8.0l8.0,0.0l0,-8z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.7,10.0l2.0,0.0l0.0,8.1l-2.0,0.0z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillColor="?attr/backgroundColor"/>
<path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M17.7,8.0l4.299999,0.0 0.0,-6.0 -20.0,20.0 15.700001,0.0z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M10.1,13.9l-8.1,8.1 8.1,0.0z"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_2.xml b/packages/SystemUI/res/drawable/stat_sys_signal_2.xml
index eb2be08..2a660a3 100644
--- a/packages/SystemUI/res/drawable/stat_sys_signal_2.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_2.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,20 +15,17 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="17dp"
- android:height="17dp"
+ android:width="17.0dp"
+ android:height="17.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.0,10.6l-12.0,12.0l12.0,0.0L14.0,10.6z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M13.900000,10.000000l-11.900000,12.000000 11.900000,0.000000z"/>
- <path
- android:pathData="M17.700001,8.000000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillColor="?attr/backgroundColor"/>
+ <path
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_3.xml b/packages/SystemUI/res/drawable/stat_sys_signal_3.xml
index 22afad0..9e0a433 100644
--- a/packages/SystemUI/res/drawable/stat_sys_signal_3.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_3.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,20 +15,17 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="17dp"
- android:height="17dp"
+ android:width="17.0dp"
+ android:height="17.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,19.900000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,9.900000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M16.700001,7.200000l-14.700001,14.700000 14.700001,0.000000z"/>
- <path
- android:pathData="M17.700001,7.900000l4.299999,0.000000 0.000000,-6.000000 -20.000000,20.000000 15.700001,0.000000z"
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillColor="?attr/backgroundColor"/>
+ <path
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="?attr/fillColor"/>
+ <path
+ android:pathData="M14.1,14.1l2.9,0.0 0.0,-6.5 -15.0,15.0 12.1,0.0z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_4.xml b/packages/SystemUI/res/drawable/stat_sys_signal_4.xml
index d1e866d..01f6703 100644
--- a/packages/SystemUI/res/drawable/stat_sys_signal_4.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_4.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,18 +15,14 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
- android:width="17dp"
- android:height="17dp"
+ android:width="17.0dp"
+ android:height="17.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
-
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
+ android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.700001,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M2.000000,22.000000l15.700001,0.000000 0.000000,-14.000000 4.299999,0.000000 0.000000,-6.000000z"/>
+ android:pathData="M21.9,17.0l-1.1,-1.1 -1.9,1.9 -1.9,-1.9 -1.1,1.1 1.9,1.9 -1.9,1.9 1.1,1.1 1.9,-1.9 1.9,1.9 1.1,-1.1 -1.9,-1.9z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml
index 7f1b715e..2de2e36 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,16 +15,13 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
+ android:height="18.41dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.200000,-1.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/backgroundColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml
index acd89be..144a7c1 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,19 +15,16 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
+ android:height="18.41dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,13.2c-0.1,0.0 -0.3,-0.1 -0.4,-0.1c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,0.0 -0.6,-0.1 -0.9,-0.1c0.0,0.0 0.0,0.0 -0.1,0.0c0.0,0.0 0.0,0.0 0.0,0.0s0.0,0.0 0.0,0.0c0.0,0.0 0.0,0.0 -0.1,0.0c-0.3,0.0 -0.6,0.0 -0.9,0.1c-0.1,0.0 -0.3,0.0 -0.4,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.1,0.0 -0.1,0.0 -0.2,0.1c-1.6,0.5 -2.7,1.3 -2.8,1.5l5.3,6.6l0.0,0.0l0.0,0.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.700002,13.2z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M13.000000,22.000000l5.500000,-6.800000c-0.200000,-0.200000 -2.300000,-1.900000 -5.500000,-1.900000s-5.300000,1.800000 -5.500000,1.900000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/backgroundColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml
index f33b25c..6b7f712 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,19 +15,16 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
+ android:height="18.41dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l4.9,0.0c-1.0,-0.7 -3.4,-2.2 -6.7,-2.2c-4.1,0.0 -6.9,2.2 -7.2,2.5l7.2,9.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.800001,12.2z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M19.000000,11.600000c-1.300000,-0.700000 -3.400000,-1.600000 -6.000000,-1.600000c-4.400000,0.000000 -7.300000,2.400000 -7.600000,2.700000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,11.600000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/backgroundColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.800001,1.9 -1.800001,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml
index 09d2e50..d34b4de 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,19 +15,16 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
+ android:height="18.41dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0l1.0,-1.2C20.0,10.6 16.8,8.0 12.0,8.0s-8.0,2.6 -8.5,3.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M19.000000,8.600000c-1.600000,-0.700000 -3.600000,-1.300000 -6.000000,-1.300000c-5.300000,0.000000 -8.900000,3.000000 -9.200000,3.200000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.600000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/backgroundColor"/>
<path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml
index fb1f584..5701356 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml
@@ -1,7 +1,7 @@
<!--
-Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
+ 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
@@ -15,16 +15,13 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
+ android:height="18.41dp"
+ android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M19.000000,8.000000l5.300000,0.000000l1.300000,-1.600000C25.100000,6.000000 20.299999,2.000000 13.000000,2.000000S0.900000,6.000000 0.400000,6.400000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l6.000000,-7.400000L19.000000,8.000000z"/>
+ android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
+ android:fillColor="?attr/fillColor"/>
<path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M21.000000,20.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000z"/>
- <path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M21.000000,10.000000l2.000000,0.000000l0.000000,8.100000l-2.000000,0.000000z"/>
+ android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
+ android:fillColor="?attr/fillColor"/>
</vector>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 6fc1bdd..8aef790 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -346,8 +346,8 @@
<string name="description_direction_left" msgid="7207478719805562165">"لغزاندن به چپ برای <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="zen_priority_introduction" msgid="3070506961866919502">"صداها و لرزشهایی به جز هشدارها، یادآوریها، رویدادها و تماسگیرندههایی که مشخص میکنید، مزاحم شما نمیشوند."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"سفارشی کردن"</string>
- <string name="zen_silence_introduction_voice" msgid="2284540992298200729">"این کار «همه» صداها و لرزشها از جمله هشدارها، موسیقی، ویدیوها و بازیها را مسدود میکند. همچنان میتوانید تماس تلفنی برقرار کنید."</string>
- <string name="zen_silence_introduction" msgid="3137882381093271568">"این کار «همه» صداها و لرزشها از جمله هشدارها، موسیقی، ویدیوها و بازیها را مسدود میکند."</string>
+ <string name="zen_silence_introduction_voice" msgid="2284540992298200729">"این کار «همه» صداها و لرزشها از جمله هشدارها، موسیقی، ویدئوها و بازیها را مسدود میکند. همچنان میتوانید تماس تلفنی برقرار کنید."</string>
+ <string name="zen_silence_introduction" msgid="3137882381093271568">"این کار «همه» صداها و لرزشها از جمله هشدارها، موسیقی، ویدئوها و بازیها را مسدود میکند."</string>
<string name="keyguard_more_overflow_text" msgid="9195222469041601365">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
<string name="speed_bump_explanation" msgid="1288875699658819755">"اعلانهای کمتر فوری در زیر"</string>
<string name="notification_tap_again" msgid="7590196980943943842">"دوباره ضربه بزنید تا باز شود"</string>
diff --git a/packages/SystemUI/res/values-fa/strings_tv.xml b/packages/SystemUI/res/values-fa/strings_tv.xml
index 2894abba..b97a6465 100644
--- a/packages/SystemUI/res/values-fa/strings_tv.xml
+++ b/packages/SystemUI/res/values-fa/strings_tv.xml
@@ -25,7 +25,7 @@
<string name="pip_pause" msgid="8412075640017218862">"مکث"</string>
<string name="pip_hold_home" msgid="340086535668778109">"کنترل PIP با نگهداشتن "<b>"HOME"</b></string>
<string name="pip_onboarding_title" msgid="7850436557670253991">"تصویر در تصویر"</string>
- <string name="pip_onboarding_description" msgid="4028124563309465267">"تا زمانی که ویدیوی دیگری را پخش کنید، این صفحه حالت ویدیو در ویدیوی شما را حفظ میکند. برای کنترل آن، دکمه "<b>"صفحه اصلی"</b>" را فشار دهید و نگه دارید."</string>
+ <string name="pip_onboarding_description" msgid="4028124563309465267">"تا زمانی که ویدئوی دیگری را پخش کنید، این صفحه حالت ویدئو در ویدئوی شما را حفظ میکند. برای کنترل آن، دکمه "<b>"صفحه اصلی"</b>" را فشار دهید و نگه دارید."</string>
<string name="pip_onboarding_button" msgid="3957426748484904611">"متوجه شدم"</string>
<string name="recents_tv_dismiss" msgid="3555093879593377731">"رد کردن"</string>
<string-array name="recents_tv_blacklist_array">
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index fd6cc4e..c781403 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -403,7 +403,7 @@
<string name="device_owned_footer" msgid="3802752663326030053">"डिवाइस को मॉनीटर किया जा सकता है"</string>
<string name="profile_owned_footer" msgid="8021888108553696069">"प्रोफ़ाइल को मॉनीटर किया जा सकता है"</string>
<string name="vpn_footer" msgid="2388611096129106812">"नेटवर्क को मॉनीटर किया जा सकता है"</string>
- <string name="branded_vpn_footer" msgid="2168111859226496230">"नेटवर्क को मॉनीटर किया जा सकता है"</string>
+ <string name="branded_vpn_footer" msgid="2168111859226496230">"नेटवर्क को मॉनिटर किया जा सकता है"</string>
<string name="monitoring_title_device_owned" msgid="7121079311903859610">"डिवाइस को मॉनीटर करना"</string>
<string name="monitoring_title_profile_owned" msgid="6790109874733501487">"प्रोफ़ाइल को मॉनीटर करना"</string>
<string name="monitoring_title" msgid="169206259253048106">"नेटवर्क को मॉनीटर करना"</string>
@@ -416,7 +416,7 @@
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="6259179342284742878">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> से कनेक्ट हैं, जो ईमेल, ऐप्स और वेबसाइटों सहित आपकी नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> से कनेक्ट हैं, जो ईमेल, ऐप्स और वेबसाइटों सहित आपकी व्यक्तिगत नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
- <string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> से कनेक्ट हैं, जो ईमेल, ऐप्लिकेशन और वेबसाइट सहित आपकी व्यक्तिगत नेटवर्क गतिविधि को मॉनीटर कर सकता है."</string>
+ <string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> से कनेक्ट हैं, जो ईमेल, ऐप्लिकेशन और वेबसाइट सहित आपकी व्यक्तिगत नेटवर्क गतिविधि को मॉनिटर कर सकता है."</string>
<string name="monitoring_description_app_work" msgid="1754325860918060897">"आपकी कार्य प्रोफ़ाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> के द्वारा प्रबंधित है. वह <xliff:g id="APPLICATION">%2$s</xliff:g> से कनेक्ट है, जो ईमेल, ऐप्स और वेबसाइटों सहित आपकी कार्य नेटवर्क गतिविधि की निगरानी कर सकता है.\n\nअधिक जानकारी के लिए, अपने नियंत्रक से संपर्क करें."</string>
<string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"आपकी कार्य प्रोफ़ाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> के द्वारा प्रबंधित है. वह <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> से कनेक्ट है, जो ईमेल, ऐप्स और वेबसाइटों सहित आपकी कार्य नेटवर्क गतिविधि की निगरानी कर सकता है.\n\nआप <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> से भी कनेक्ट हैं, जो आपकी व्यक्तिगत नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
<string name="monitoring_description_vpn_app_device_owned" msgid="4970443827043261703">"आपका डिवाइस <xliff:g id="ORGANIZATION">%1$s</xliff:g> के द्वारा प्रबंधित है.\n\nआपका नियंत्रक सेटिंग, कॉर्पोरेट ऐक्सेस, ऐप्स, आपके डिवाइस से संबद्ध डेटा और आपके डिवाइस की स्थान जानकारी की निगरानी और उसका प्रबंधन कर सकता है.\n\nआप <xliff:g id="APPLICATION">%2$s</xliff:g> से कनेक्ट हैं, जो ईमेल, ऐप्स और वेबसाइटों सहित आपकी नेटवर्क गतिविधि की निगरानी कर सकता है.\n\nअधिक जानकारी के लिए, अपने नियंत्रक से संपर्क करें."</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 0d414f4..e5bc8b9 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -217,7 +217,7 @@
<style name="Animation.StatusBar">
</style>
- <style name="systemui_theme" parent="@android:style/Theme.DeviceDefault" />
+ <style name="systemui_theme" parent="@*android:style/Theme.DeviceDefault.Settings.Dark" />
<style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light">
<item name="android:colorAccent">@color/remote_input_accent</item>
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTracker.java b/packages/SystemUI/src/com/android/systemui/LatencyTracker.java
new file mode 100644
index 0000000..cc25ee3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTracker.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.Log;
+import android.util.SparseLongArray;
+
+/**
+ * Class to track various latencies in SystemUI. It then outputs the latency to logcat so these
+ * latencies can be captured by tests and then used for dashboards.
+ */
+public class LatencyTracker {
+
+ private static final String ACTION_RELOAD_PROPERTY =
+ "com.android.systemui.RELOAD_LATENCY_TRACKER_PROPERTY";
+
+ private static final String TAG = "LatencyTracker";
+
+ /**
+ * Time it takes until the first frame of the notification panel to be displayed while expanding
+ */
+ public static final int ACTION_EXPAND_PANEL = 0;
+
+ /**
+ * Time it takes until the first frame of recents is drawn after invoking it with the button.
+ */
+ public static final int ACTION_TOGGLE_RECENTS = 1;
+
+ /**
+ * Time between we get a fingerprint acquired signal until we start with the unlock animation
+ */
+ public static final int ACTION_FINGERPRINT_WAKE_AND_UNLOCK = 2;
+
+ private static final String[] NAMES = new String[] {
+ "expand panel",
+ "toggle recents",
+ "fingerprint wake-and-unlock" };
+
+ private static LatencyTracker sLatencyTracker;
+
+ private final SparseLongArray mStartRtc = new SparseLongArray();
+ private boolean mEnabled;
+
+ public static LatencyTracker getInstance(Context context) {
+ if (sLatencyTracker == null) {
+ sLatencyTracker = new LatencyTracker(context);
+ }
+ return sLatencyTracker;
+ }
+
+ private LatencyTracker(Context context) {
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reloadProperty();
+ }
+ }, new IntentFilter(ACTION_RELOAD_PROPERTY));
+ reloadProperty();
+ }
+
+ private void reloadProperty() {
+ mEnabled = SystemProperties.getBoolean("debug.systemui.latency_tracking", false);
+ }
+
+ public static boolean isEnabled(Context ctx) {
+ return Build.IS_DEBUGGABLE && getInstance(ctx).mEnabled;
+ }
+
+ /**
+ * Notifies that an action is starting. This needs to be called from the main thread.
+ *
+ * @param action The action to start. One of the ACTION_* values.
+ */
+ public void onActionStart(int action) {
+ if (!mEnabled) {
+ return;
+ }
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, NAMES[action], 0);
+ mStartRtc.put(action, SystemClock.elapsedRealtime());
+ }
+
+ /**
+ * Notifies that an action has ended. This needs to be called from the main thread.
+ *
+ * @param action The action to end. One of the ACTION_* values.
+ */
+ public void onActionEnd(int action) {
+ if (!mEnabled) {
+ return;
+ }
+ long endRtc = SystemClock.elapsedRealtime();
+ long startRtc = mStartRtc.get(action, -1);
+ if (startRtc == -1) {
+ return;
+ }
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, NAMES[action], 0);
+ long duration = endRtc - startRtc;
+ Log.i(TAG, "action=" + action + " latency=" + duration);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index b9ae585..19ae295 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -49,7 +49,6 @@
Key.QS_WORK_ADDED,
})
public @interface Key {
- @Deprecated
String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime";
String DEBUG_MODE_ENABLED = "debugModeEnabled";
String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 52b5a54..e300aff 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -16,18 +16,24 @@
package com.android.systemui;
+import android.app.ActivityThread;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.os.Process;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Log;
+import com.android.systemui.plugins.OverlayPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.HashMap;
import java.util.Map;
@@ -106,6 +112,13 @@
}
}, filter);
} else {
+ // We don't need to startServices for sub-process that is doing some tasks.
+ // (screenshots, sweetsweetdesserts or tuner ..)
+ String processName = ActivityThread.currentProcessName();
+ ApplicationInfo info = getApplicationInfo();
+ if (processName != null && processName.startsWith(info.processName + ":")) {
+ return;
+ }
// For a secondary user, boot-completed will never be called because it has already
// been broadcasted on startup for the primary SystemUI process. Instead, for
// components which require the SystemUI component to be initialized per-user, we
@@ -174,6 +187,18 @@
mServices[i].onBootCompleted();
}
}
+ PluginManager.getInstance(this).addPluginListener(OverlayPlugin.ACTION,
+ new PluginListener<OverlayPlugin>() {
+ @Override
+ public void onPluginConnected(OverlayPlugin plugin) {
+ PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
+ if (phoneStatusBar != null) {
+ plugin.setup(phoneStatusBar.getStatusBarWindow(),
+ phoneStatusBar.getNavigationBarView());
+ }
+ }
+ }, OverlayPlugin.VERSION, true /* Allow multiple plugins */);
+
mServicesStarted = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index aa6e88c..b9e8acb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -976,6 +976,7 @@
* if there is a secure lock pattern.
*/
public void onDreamingStarted() {
+ KeyguardUpdateMonitor.getInstance(mContext).dispatchDreamingStarted();
synchronized (this) {
if (mDeviceInteractive
&& mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
@@ -988,6 +989,7 @@
* A dream stopped.
*/
public void onDreamingStopped() {
+ KeyguardUpdateMonitor.getInstance(mContext).dispatchDreamingStopped();
synchronized (this) {
if (mDeviceInteractive) {
cancelDoKeyguardLaterLocked();
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index b831235..8a500c3 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -31,6 +31,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -73,7 +74,7 @@
private final Context mContext;
private final NotificationManager mNoMan;
private final PowerManager mPowerMan;
- private final Handler mHandler = new Handler();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Receiver mReceiver = new Receiver();
private final Intent mOpenBatterySettings = settings(Intent.ACTION_POWER_USAGE_SUMMARY);
@@ -89,9 +90,10 @@
private boolean mInvalidCharger;
private SystemUIDialog mSaverConfirmation;
- public PowerNotificationWarnings(Context context, PhoneStatusBar phoneStatusBar) {
+ public PowerNotificationWarnings(Context context, NotificationManager notificationManager,
+ PhoneStatusBar phoneStatusBar) {
mContext = context;
- mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ mNoMan = notificationManager;
mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mReceiver.init();
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 109a456..b651f2d9 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -16,6 +16,7 @@
package com.android.systemui.power;
+import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -30,10 +31,8 @@
import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
-
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -60,7 +59,10 @@
public void start() {
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
- mWarnings = new PowerNotificationWarnings(mContext, getComponent(PhoneStatusBar.class));
+ mWarnings = new PowerNotificationWarnings(
+ mContext,
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE),
+ getComponent(PhoneStatusBar.class));
ContentObserver obs = new ContentObserver(mHandler) {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index c432096..867f3b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -63,7 +63,7 @@
// Bind retry control.
private static final int MAX_BIND_RETRIES = 5;
- private static final int BIND_RETRY_DELAY = 1000;
+ private static final int DEFAULT_BIND_RETRY_DELAY = 1000;
// Shared prefs that hold tile lifecycle info.
private static final String TILES = "tiles_prefs";
@@ -81,8 +81,8 @@
private IBinder mClickBinder;
private int mBindTryCount;
+ private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
private boolean mBound;
- @VisibleForTesting
boolean mReceiverRegistered;
private boolean mUnbindImmediate;
private TileChangeListener mChangeListener;
@@ -117,6 +117,10 @@
}
}
+ public void setBindRetryDelay(int delayMs) {
+ mBindRetryDelay = delayMs;
+ }
+
public boolean isActiveTile() {
try {
ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
@@ -264,7 +268,7 @@
setBindService(true);
}
}
- }, BIND_RETRY_DELAY);
+ }, mBindRetryDelay);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index a7d7df5..7207463 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -34,7 +34,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
@@ -47,7 +46,6 @@
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
-import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
@@ -252,19 +250,6 @@
registerWithSystemUser();
}
putComponent(Recents.class, this);
-
- // Migrate the old stack active time if necessary, otherwise, it will already be managed
- // when the tasks are loaded in the system. See TaskPersister.restoreTasksForUserLocked().
- long lastVisibleTaskActiveTime = Prefs.getLong(mContext,
- Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
- if (lastVisibleTaskActiveTime != -1) {
- long uptime = SystemClock.elapsedRealtime();
- Settings.Secure.putLongForUser(mContext.getContentResolver(),
- Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME,
- uptime - Math.max(0, System.currentTimeMillis() - lastVisibleTaskActiveTime),
- processUser);
- Prefs.remove(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME);
- }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 1e41870..784d2ba 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -20,7 +20,6 @@
import android.app.ActivityOptions;
import android.app.TaskStackBuilder;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -41,7 +40,9 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.systemui.DejankUtils;
import com.android.systemui.Interpolators;
+import com.android.systemui.LatencyTracker;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.events.EventBus;
@@ -171,6 +172,13 @@
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
// When the screen turns off, dismiss Recents to Home
dismissRecentsToHomeIfVisible(false);
+ } else if (action.equals(Intent.ACTION_TIME_CHANGED)) {
+ // For the time being, if the time changes, then invalidate the
+ // last-stack-active-time, this ensures that we will just show the last N tasks
+ // the next time that Recents loads, but prevents really old tasks from showing
+ // up if the task time is set forward.
+ Prefs.putLong(RecentsActivity.this, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
+ 0);
}
}
};
@@ -181,6 +189,11 @@
public boolean onPreDraw() {
mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
EventBus.getDefault().post(new RecentsDrawnEvent());
+ if (LatencyTracker.isEnabled(getApplicationContext())) {
+ DejankUtils.postAfterTraversal(() -> LatencyTracker.getInstance(
+ getApplicationContext()).onActionEnd(
+ LatencyTracker.ACTION_TOGGLE_RECENTS));
+ }
return true;
}
};
@@ -316,6 +329,7 @@
// Register the broadcast receiver to handle messages when the screen is turned off
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
registerReceiver(mSystemBroadcastReceiver, filter);
getWindow().addPrivateFlags(LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION);
@@ -793,19 +807,14 @@
EventBus.getDefault().dump(prefix, writer);
Recents.getTaskLoader().dump(prefix, writer);
- ContentResolver cr = getContentResolver();
- long lastPersistUptime = Settings.Secure.getLong(cr,
- Settings.Secure.TASK_PERSISTER_LAST_WRITE_UPTIME, 0);
- long lastVisibleTaskActiveUptime = Settings.Secure.getLongForUser(cr,
- Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME,
- SystemClock.elapsedRealtime(), Recents.getSystemServices().getCurrentUser());
-
String id = Integer.toHexString(System.identityHashCode(this));
+ long lastStackActiveTime = Prefs.getLong(this,
+ Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
writer.print(prefix); writer.print(TAG);
writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
- writer.print(" lastPersistUptime="); writer.print(lastPersistUptime);
- writer.print(" lastVisibleTaskActiveUptime="); writer.print(lastVisibleTaskActiveUptime);
+ writer.print(" lastStackTaskActiveTime="); writer.print(lastStackActiveTime);
+ writer.print(" currentTime="); writer.print(System.currentTimeMillis());
writer.print(" [0x"); writer.print(id); writer.print("]");
writer.println();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 64d9831..9d9e27389 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -60,7 +60,6 @@
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -76,7 +75,6 @@
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
import com.android.internal.os.BackgroundThread;
import com.android.systemui.R;
@@ -201,9 +199,6 @@
*/
private List<TaskStackListener> mTaskStackListeners = new ArrayList<>();
- /** Test constructor */
- @VisibleForTesting public SystemServicesProxy() {}
-
/** Private constructor */
private SystemServicesProxy(Context context) {
mAccm = AccessibilityManager.getInstance(context);
@@ -305,7 +300,7 @@
rti.baseIntent = new Intent();
rti.baseIntent.setComponent(cn);
rti.description = description;
- rti.firstActiveTime = rti.lastActiveTime = SystemClock.elapsedRealtime();
+ rti.firstActiveTime = rti.lastActiveTime = i;
if (i % 2 == 0) {
rti.taskDescription = new ActivityManager.TaskDescription(description,
Bitmap.createBitmap(mDummyIcon), null,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index ecd48e1..1278b73 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -24,15 +24,13 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
@@ -58,11 +56,6 @@
private static int SESSION_BEGIN_TIME = 1000 /* ms/s */ * 60 /* s/min */ * 60 /* min/hr */ *
6 /* hrs */;
- @VisibleForTesting
- public interface SystemTimeProvider {
- public long getTime();
- }
-
/** The set of conditions to load tasks. */
public static class Options {
public int runningTaskId = -1;
@@ -74,46 +67,15 @@
public int numVisibleTaskThumbnails = 0;
}
- private Context mContext;
- @VisibleForTesting private SystemServicesProxy mSystemServicesProxy;
+ Context mContext;
- private List<ActivityManager.RecentTaskInfo> mRawTasks;
- private long mLastVisibileTaskActiveTime;
- private TaskStack mStack;
- private ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>();
- private SystemTimeProvider mTimeProvider = new SystemTimeProvider() {
- @Override
- public long getTime() {
- return SystemClock.elapsedRealtime();
- }
- };
+ List<ActivityManager.RecentTaskInfo> mRawTasks;
+ TaskStack mStack;
+ ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>();
- @VisibleForTesting
- public RecentsTaskLoadPlan(Context context, SystemServicesProxy ssp) {
+ /** Package level ctor */
+ RecentsTaskLoadPlan(Context context) {
mContext = context;
- mSystemServicesProxy = ssp;
- }
-
- @VisibleForTesting
- public void setInternals(List<ActivityManager.RecentTaskInfo> tasks,
- final long currentTime, long lastVisibleTaskActiveTime) {
- setInternals(tasks, MIN_NUM_TASKS, currentTime, lastVisibleTaskActiveTime,
- SESSION_BEGIN_TIME);
- }
-
- @VisibleForTesting
- public void setInternals(List<ActivityManager.RecentTaskInfo> tasks, int minNumTasks,
- final long currentTime, long lastVisibleTaskActiveTime, int sessionBeginTime) {
- mRawTasks = tasks;
- mLastVisibileTaskActiveTime = lastVisibleTaskActiveTime;
- mTimeProvider = new SystemTimeProvider() {
- @Override
- public long getTime() {
- return currentTime;
- }
- };
- MIN_NUM_TASKS = minNumTasks;
- SESSION_BEGIN_TIME = sessionBeginTime;
}
private void updateCurrentQuietProfilesCache(int currentUserId) {
@@ -141,13 +103,9 @@
public synchronized void preloadRawTasks(boolean includeFrontMostExcludedTask) {
int currentUserId = UserHandle.USER_CURRENT;
updateCurrentQuietProfilesCache(currentUserId);
- mRawTasks = mSystemServicesProxy.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
- mLastVisibileTaskActiveTime = RecentsDebugFlags.Static.EnableMockTasks
- ? SystemClock.elapsedRealtime()
- : Settings.Secure.getLongForUser(mContext.getContentResolver(),
- Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME,
- 0, currentUserId);
// Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(mRawTasks);
@@ -176,9 +134,12 @@
R.string.accessibility_recents_item_will_be_dismissed);
String appInfoDescFormat = mContext.getString(
R.string.accessibility_recents_item_open_app_info);
- boolean updatedLastVisibleTaskActiveTime = false;
- long newLastVisibileTaskActiveTime = 0;
- long currentTime = mTimeProvider.getTime();
+ long lastStackActiveTime = Prefs.getLong(mContext,
+ Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, 0);
+ if (RecentsDebugFlags.Static.EnableMockTasks) {
+ lastStackActiveTime = 0;
+ }
+ long newLastStackActiveTime = -1;
int taskCount = mRawTasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
@@ -187,20 +148,19 @@
Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
t.userId, t.firstActiveTime, t.lastActiveTime);
- // Only show the task if it is freeform, or later than the last visible task active time
- // and either recently used, or within the last five tasks
- boolean isFreeformTask = mSystemServicesProxy.isFreeformStack(t.stackId);
- boolean isRecentlyUsedTask = t.lastActiveTime >= (currentTime - SESSION_BEGIN_TIME);
- boolean isMoreRecentThanLastVisible = t.lastActiveTime >= mLastVisibileTaskActiveTime;
- boolean isStackTask = isFreeformTask || (isMoreRecentThanLastVisible &&
- (isRecentlyUsedTask || i >= (taskCount - MIN_NUM_TASKS)));
- boolean isLaunchTarget = t.persistentId == runningTaskId;
+ // This task is only shown in the stack if it statisfies the historical time or min
+ // number of tasks constraints. Freeform tasks are also always shown.
+ boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
+ boolean isStackTask = isFreeformTask || !isHistoricalTask(t) ||
+ (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS));
+ boolean isLaunchTarget = taskKey.id == runningTaskId;
- // If this is the first task satisfying the stack constraints, update the baseline
- // at which we show visible tasks
- if (isStackTask && !updatedLastVisibleTaskActiveTime) {
- newLastVisibileTaskActiveTime = t.lastActiveTime;
- updatedLastVisibleTaskActiveTime = true;
+ // The last stack active time is the baseline for which we show visible tasks. Since
+ // the system will store all the tasks, we don't want to show the tasks prior to the
+ // last visible ones, otherwise, as you dismiss them, the previous tasks may satisfy
+ // the other stack-task constraints.
+ if (isStackTask && newLastStackActiveTime < 0) {
+ newLastStackActiveTime = t.lastActiveTime;
}
// Load the title, icon, and color
@@ -228,12 +188,9 @@
affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
affiliatedTasks.put(taskKey.id, taskKey);
}
- if (updatedLastVisibleTaskActiveTime &&
- newLastVisibileTaskActiveTime != mLastVisibileTaskActiveTime) {
- Settings.Secure.putLongForUser(mContext.getContentResolver(),
- Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME,
- newLastVisibileTaskActiveTime, UserHandle.USER_CURRENT);
- mLastVisibileTaskActiveTime = newLastVisibileTaskActiveTime;
+ if (newLastStackActiveTime != -1) {
+ Prefs.putLong(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
+ newLastStackActiveTime);
}
// Initialize the stacks
@@ -298,4 +255,11 @@
}
return false;
}
+
+ /**
+ * Returns whether this task is too old to be shown.
+ */
+ private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {
+ return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index e0eda37..ba31e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -30,7 +30,6 @@
import android.util.Log;
import android.util.LruCache;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
@@ -287,20 +286,6 @@
}
};
- @VisibleForTesting
- public RecentsTaskLoader() {
- mActivityInfoCache = null;
- mIconCache = null;
- mThumbnailCache = null;
- mActivityLabelCache = null;
- mContentDescriptionCache = null;
- mLoadQueue = null;
- mLoader = null;
-
- mMaxThumbnailCacheSize = 0;
- mMaxIconCacheSize = 0;
- }
-
public RecentsTaskLoader(Context context) {
Resources res = context.getResources();
mDefaultTaskBarBackgroundColor =
@@ -347,8 +332,7 @@
/** Creates a new plan for loading the recent tasks. */
public RecentsTaskLoadPlan createLoadPlan(Context context) {
- RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context,
- Recents.getSystemServices());
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
return plan;
}
@@ -471,8 +455,7 @@
/**
* Returns the cached task label if the task key is not expired, updating the cache if it is.
*/
- @VisibleForTesting public String getAndUpdateActivityTitle(Task.TaskKey taskKey,
- ActivityManager.TaskDescription td) {
+ String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Return the task description label if it exists
@@ -500,8 +483,7 @@
* Returns the cached task content description if the task key is not expired, updating the
* cache if it is.
*/
- @VisibleForTesting public String getAndUpdateContentDescription(Task.TaskKey taskKey,
- Resources res) {
+ String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Return the cached content description if it exists
@@ -525,8 +507,8 @@
/**
* Returns the cached task icon if the task key is not expired, updating the cache if it is.
*/
- @VisibleForTesting public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey,
- ActivityManager.TaskDescription td, Resources res, boolean loadIfNotCached) {
+ Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
+ Resources res, boolean loadIfNotCached) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Return the cached activity icon if it exists
@@ -560,8 +542,7 @@
/**
* Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
*/
- @VisibleForTesting public Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey,
- boolean loadIfNotCached) {
+ Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Return the cached thumbnail if it exists
@@ -589,7 +570,7 @@
* Returns the task's primary color if possible, defaulting to the default color if there is
* no specified primary color.
*/
- @VisibleForTesting public int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
+ int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
if (td != null && td.getPrimaryColor() != 0) {
return td.getPrimaryColor();
}
@@ -599,7 +580,7 @@
/**
* Returns the task's background color if possible.
*/
- @VisibleForTesting public int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
+ int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
if (td != null && td.getBackgroundColor() != 0) {
return td.getBackgroundColor();
}
@@ -610,7 +591,7 @@
* Returns the activity info for the given task key, retrieving one from the system if the
* task key is expired.
*/
- @VisibleForTesting public ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
+ ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
SystemServicesProxy ssp = Recents.getSystemServices();
ComponentName cn = taskKey.getComponent();
ActivityInfo activityInfo = mActivityInfoCache.get(cn);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 4191f52..86a0315 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -290,10 +290,7 @@
*/
public boolean isFreeformTask() {
SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp != null) {
- return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId);
- }
- return false;
+ return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId);
}
/** Notifies the callback listeners that this task has been loaded */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index a6536a83..a7132e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -113,8 +113,11 @@
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH;
@@ -268,6 +271,8 @@
protected boolean mVrMode;
+ private Set<String> mNonBlockablePkgs;
+
@Override // NotificationData.Environment
public boolean isDeviceProvisioned() {
return mDeviceProvisioned;
@@ -309,6 +314,7 @@
mUsersAllowingPrivateNotifications.clear();
mUsersAllowingNotifications.clear();
// ... and refresh all the notifications
+ updateLockscreenNotificationSetting();
updateNotifications();
}
};
@@ -714,7 +720,7 @@
mSettingsObserver);
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
- mSettingsObserver,
+ mLockscreenSettingsObserver,
UserHandle.USER_ALL);
if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
mContext.getContentResolver().registerContentObserver(
@@ -828,6 +834,9 @@
Slog.e(TAG, "Failed to register VR mode state listener: " + e);
}
+ mNonBlockablePkgs = new HashSet<String>();
+ Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray(
+ com.android.internal.R.array.config_nonBlockableNotificationPackages));
}
protected void notifyUserAboutHiddenNotifications() {
@@ -1108,7 +1117,8 @@
settingsButton.setVisibility(View.GONE);
}
- guts.bindImportance(pmUser, sbn, mNotificationData.getImportance(sbn.getKey()));
+ guts.bindImportance(pmUser, sbn, mNonBlockablePkgs,
+ mNotificationData.getImportance(sbn.getKey()));
final TextView doneButton = (TextView) guts.findViewById(R.id.done);
doneButton.setText(R.string.notification_done);
@@ -1948,9 +1958,18 @@
.getIdentifier();
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
&& mKeyguardManager.isDeviceLocked(userId)) {
- if (startWorkChallengeIfNecessary(userId,
- intent.getIntentSender(), notificationKey)) {
- // Show work challenge, do not run pendingintent and
+ boolean canBypass = false;
+ try {
+ canBypass = ActivityManagerNative.getDefault()
+ .canBypassWorkChallenge(intent);
+ } catch (RemoteException e) {
+ }
+ // For direct-boot aware activities, they can be shown when
+ // the device is still locked without triggering the work
+ // challenge.
+ if ((!canBypass) && startWorkChallengeIfNecessary(userId,
+ intent.getIntentSender(), notificationKey)) {
+ // Show work challenge, do not run PendingIntent and
// remove notification
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
index 2045ec8..1d7bede 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
@@ -56,6 +56,8 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDismissButton.setText(R.string.clear_all_notifications_text);
+ mDismissButton.setContentDescription(
+ mContext.getString(R.string.accessibility_clear_all));
}
public boolean isButtonVisible() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 68de16b..caf5447 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -905,6 +905,9 @@
}
public void resetTranslation() {
+ if (mTranslateAnim != null) {
+ mTranslateAnim.cancel();
+ }
if (mTranslateableViews != null) {
for (int i = 0; i < mTranslateableViews.size(); i++) {
mTranslateableViews.get(i).setTranslationX(0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index 88f37a3..8b4225a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -174,7 +174,10 @@
private void drawBackgroundCircle(Canvas canvas) {
if (mCircleRadius > 0 || mFinishing) {
- if (mFinishing && mSupportHardware) {
+ if (mFinishing && mSupportHardware && mHwCenterX != null) {
+ // Our hardware drawing proparties can be null if the finishing started but we have
+ // never drawn before. In that case we are not doing a render thread animation
+ // anyway, so we need to use the normal drawing.
DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
displayListCanvas.drawCircle(mHwCenterX, mHwCenterY, mHwCircleRadius,
mHwCirclePaint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 9fd09d9..78e56c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -205,7 +205,7 @@
&& MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
singleLineWidthSpec = MeasureSpec.makeMeasureSpec(
width - mSingleLineWidthIndention + mSingleLineView.getPaddingEnd(),
- MeasureSpec.AT_MOST);
+ MeasureSpec.EXACTLY);
}
mSingleLineView.measure(singleLineWidthSpec,
MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index c497cfd..62d730a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -50,6 +50,8 @@
import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.tuner.TunerService;
+import java.util.Set;
+
/**
* The guts of a notification revealed when performing a long press.
*/
@@ -173,7 +175,7 @@
}
void bindImportance(final PackageManager pm, final StatusBarNotification sbn,
- final int importance) {
+ final Set<String> nonBlockablePkgs, final int importance) {
mINotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
mStartingUserImportance = NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
@@ -182,24 +184,26 @@
mINotificationManager.getImportance(sbn.getPackageName(), sbn.getUid());
} catch (RemoteException e) {}
mNotificationImportance = importance;
- boolean systemApp = false;
+ boolean nonBlockable = false;
try {
final PackageInfo info =
pm.getPackageInfo(sbn.getPackageName(), PackageManager.GET_SIGNATURES);
- systemApp = Utils.isSystemPackage(getResources(), pm, info);
+ nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
} catch (PackageManager.NameNotFoundException e) {
// unlikely.
}
+ if (nonBlockablePkgs != null) {
+ nonBlockable |= nonBlockablePkgs.contains(sbn.getPackageName());
+ }
final View importanceSlider = findViewById(R.id.importance_slider);
final View importanceButtons = findViewById(R.id.importance_buttons);
if (mShowSlider) {
- bindSlider(importanceSlider, systemApp);
+ bindSlider(importanceSlider, nonBlockable);
importanceSlider.setVisibility(View.VISIBLE);
importanceButtons.setVisibility(View.GONE);
} else {
-
- bindToggles(importanceButtons, mStartingUserImportance, systemApp);
+ bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
importanceButtons.setVisibility(View.VISIBLE);
importanceSlider.setVisibility(View.GONE);
}
@@ -239,7 +243,7 @@
}
private void bindToggles(final View importanceButtons, final int importance,
- final boolean systemApp) {
+ final boolean nonBlockable) {
((RadioGroup) importanceButtons).setOnCheckedChangeListener(
new RadioGroup.OnCheckedChangeListener() {
@Override
@@ -250,7 +254,7 @@
mBlock = (RadioButton) importanceButtons.findViewById(R.id.block_importance);
mSilent = (RadioButton) importanceButtons.findViewById(R.id.silent_importance);
mReset = (RadioButton) importanceButtons.findViewById(R.id.reset_importance);
- if (systemApp) {
+ if (nonBlockable) {
mBlock.setVisibility(View.GONE);
mReset.setText(mContext.getString(R.string.do_not_silence));
} else {
@@ -265,7 +269,7 @@
}
}
- private void bindSlider(final View importanceSlider, final boolean systemApp) {
+ private void bindSlider(final View importanceSlider, final boolean nonBlockable) {
mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
mInactiveSliderTint = loadColorStateList(R.color.notification_guts_disabled_slider_color);
@@ -273,7 +277,7 @@
mImportanceTitle = ((TextView) importanceSlider.findViewById(R.id.title));
mSeekBar = (SeekBar) importanceSlider.findViewById(R.id.seekbar);
- final int minProgress = systemApp ?
+ final int minProgress = nonBlockable ?
NotificationListenerService.Ranking.IMPORTANCE_MIN
: NotificationListenerService.Ranking.IMPORTANCE_NONE;
mSeekBar.setMax(NotificationListenerService.Ranking.IMPORTANCE_MAX);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 2c3e805..82867c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -16,7 +16,11 @@
package com.android.systemui.statusbar.phone;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -26,6 +30,7 @@
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.LatencyTracker;
import com.android.systemui.keyguard.KeyguardViewMediator;
/**
@@ -37,6 +42,8 @@
private static final boolean DEBUG_FP_WAKELOCK = KeyguardConstants.DEBUG_FP_WAKELOCK;
private static final long FINGERPRINT_WAKELOCK_TIMEOUT_MS = 15 * 1000;
private static final String FINGERPRINT_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
+ private static final String ACTION_FINGERPRINT_WAKE_FAKE =
+ "com.android.systemui.ACTION_FINGERPRINT_WAKE_FAKE";
/**
* Mode in which we don't need to wake up the device when we get a fingerprint.
@@ -94,6 +101,8 @@
private KeyguardViewMediator mKeyguardViewMediator;
private ScrimController mScrimController;
private PhoneStatusBar mPhoneStatusBar;
+ private final UnlockMethodCache mUnlockMethodCache;
+ private final Context mContext;
private boolean mGoingToSleep;
private int mPendingAuthenticatedUserId = -1;
@@ -102,7 +111,9 @@
DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator,
ScrimController scrimController,
- PhoneStatusBar phoneStatusBar) {
+ PhoneStatusBar phoneStatusBar,
+ UnlockMethodCache unlockMethodCache) {
+ mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
mUpdateMonitor.registerCallback(this);
@@ -111,6 +122,15 @@
mKeyguardViewMediator = keyguardViewMediator;
mScrimController = scrimController;
mPhoneStatusBar = phoneStatusBar;
+ mUnlockMethodCache = unlockMethodCache;
+ if (Build.IS_DEBUGGABLE) {
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ fakeWakeAndUnlock();
+ }
+ }, new IntentFilter(ACTION_FINGERPRINT_WAKE_FAKE));
+ }
}
public void setStatusBarKeyguardViewManager(
@@ -139,11 +159,20 @@
}
}
+ public void fakeWakeAndUnlock() {
+ onFingerprintAcquired();
+ onFingerprintAuthenticated(KeyguardUpdateMonitor.getCurrentUser());
+ }
+
@Override
public void onFingerprintAcquired() {
Trace.beginSection("FingerprintUnlockController#onFingerprintAcquired");
releaseFingerprintWakeLock();
if (!mUpdateMonitor.isDeviceInteractive()) {
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionStart(
+ LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK);
+ }
mWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, FINGERPRINT_WAKE_LOCK_NAME);
Trace.beginSection("acquiring wake-and-unlock");
@@ -263,7 +292,7 @@
return MODE_ONLY_WAKE;
} else if (mDozeScrimController.isPulsing() && unlockingAllowed) {
return MODE_WAKE_AND_UNLOCK_PULSING;
- } else if (unlockingAllowed) {
+ } else if (unlockingAllowed || !mUnlockMethodCache.isMethodSecure()) {
return MODE_WAKE_AND_UNLOCK;
} else {
return MODE_SHOW_BOUNCER;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index d4f98c3..d94def9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -308,6 +308,7 @@
super.setVisibility(visibility);
if (visibility != View.VISIBLE) {
mSystemIconsSuperContainer.animate().cancel();
+ mSystemIconsSuperContainer.setTranslationX(0);
mMultiUserSwitch.animate().cancel();
mMultiUserSwitch.setAlpha(1f);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 222e8df..9e5b881 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -43,6 +43,7 @@
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.inputmethod.InputMethodManager;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
@@ -52,7 +53,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-public class NavigationBarView extends LinearLayout {
+public class NavigationBarView extends FrameLayout {
final static boolean DEBUG = false;
final static String TAG = "StatusBar/NavBarView";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index a6a5742..c6aec73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InputDevice;
@@ -32,9 +33,11 @@
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
+import com.android.systemui.DejankUtils;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.Interpolators;
+import com.android.systemui.LatencyTracker;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.doze.DozeLog;
@@ -115,6 +118,7 @@
protected boolean mExpanding;
private boolean mGestureWaitForTouchSlop;
private boolean mIgnoreXTouchSlop;
+ private boolean mExpandLatencyTracking;
private Runnable mPeekRunnable = new Runnable() {
@Override
public void run() {
@@ -215,6 +219,14 @@
}
}
+ public void startExpandLatencyTracking() {
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionStart(
+ LatencyTracker.ACTION_EXPAND_PANEL);
+ mExpandLatencyTracking = true;
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mInstantExpanding || mTouchDisabled
@@ -739,6 +751,11 @@
}
public void setExpandedHeightInternal(float h) {
+ if (mExpandLatencyTracking && h != 0f) {
+ DejankUtils.postAfterTraversal(() -> LatencyTracker.getInstance(mContext).onActionEnd(
+ LatencyTracker.ACTION_EXPAND_PANEL));
+ mExpandLatencyTracking = false;
+ }
float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
if (mHeightAnimator == null) {
float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index ebd4798..4c40da0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -132,6 +132,7 @@
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.Interpolators;
+import com.android.systemui.LatencyTracker;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
@@ -665,6 +666,15 @@
private boolean mNoAnimationOnNextBarModeChange;
private FalsingManager mFalsingManager;
+ private KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onDreamingStateChanged(boolean dreaming) {
+ if (dreaming) {
+ maybeEscalateHeadsUp();
+ }
+ }
+ };
+
@Override
public void start() {
mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
@@ -702,8 +712,8 @@
mUnlockMethodCache.addListener(this);
startKeyguard();
+ KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateCallback);
mDozeServiceHost = new DozeServiceHost();
- KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mDozeServiceHost);
putComponent(DozeHost.class, mDozeServiceHost);
putComponent(PhoneStatusBar.class, this);
@@ -1232,7 +1242,7 @@
KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
mFingerprintUnlockController = new FingerprintUnlockController(mContext,
mStatusBarWindowManager, mDozeScrimController, keyguardViewMediator,
- mScrimController, this);
+ mScrimController, this, UnlockMethodCache.getInstance(mContext));
mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
getBouncerContainer(), mStatusBarWindowManager, mScrimController,
mFingerprintUnlockController);
@@ -1291,6 +1301,10 @@
private View.OnClickListener mRecentsClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionStart(
+ LatencyTracker.ACTION_TOGGLE_RECENTS);
+ }
awakenDreams();
toggleRecentApps();
}
@@ -4469,6 +4483,7 @@
}
if (state == StatusBarState.KEYGUARD) {
removeRemoteInputEntriesKeptUntilCollapsed();
+ maybeEscalateHeadsUp();
}
mState = state;
mGroupManager.setStatusBarState(state);
@@ -4664,44 +4679,47 @@
final Runnable clickPendingViewRunnable = new Runnable() {
@Override
public void run() {
- if (mPendingWorkRemoteInputView != null) {
- final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
- ViewParent p = pendingWorkRemoteInputView.getParent();
- while (p != null) {
- if (p instanceof ExpandableNotificationRow) {
- final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
- ViewParent viewParent = row.getParent();
- if (viewParent instanceof NotificationStackScrollLayout) {
- final NotificationStackScrollLayout scrollLayout =
- (NotificationStackScrollLayout) viewParent;
- row.makeActionsVisibile();
- row.post(new Runnable() {
- @Override
- public void run() {
- final Runnable finishScrollingCallback = new Runnable()
- {
- @Override
- public void run() {
- mPendingWorkRemoteInputView.callOnClick();
- mPendingWorkRemoteInputView = null;
- scrollLayout.setFinishScrollingCallback(null);
- }
- };
- if (scrollLayout.scrollTo(row)) {
- // It scrolls! So call it when it's finished.
- scrollLayout.setFinishScrollingCallback(
- finishScrollingCallback);
- } else {
- // It does not scroll, so call it now!
- finishScrollingCallback.run();
- }
- }
- });
- }
- break;
- }
- p = p.getParent();
+ final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
+ if (pendingWorkRemoteInputView == null) {
+ return;
+ }
+
+ // Climb up the hierarchy until we get to the container for this row.
+ ViewParent p = pendingWorkRemoteInputView.getParent();
+ while (!(p instanceof ExpandableNotificationRow)) {
+ if (p == null) {
+ return;
}
+ p = p.getParent();
+ }
+
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
+ ViewParent viewParent = row.getParent();
+ if (viewParent instanceof NotificationStackScrollLayout) {
+ final NotificationStackScrollLayout scrollLayout =
+ (NotificationStackScrollLayout) viewParent;
+ row.makeActionsVisibile();
+ row.post(new Runnable() {
+ @Override
+ public void run() {
+ final Runnable finishScrollingCallback = new Runnable() {
+ @Override
+ public void run() {
+ mPendingWorkRemoteInputView.callOnClick();
+ mPendingWorkRemoteInputView = null;
+ scrollLayout.setFinishScrollingCallback(null);
+ }
+ };
+ if (scrollLayout.scrollTo(row)) {
+ // It scrolls! So call it when it's finished.
+ scrollLayout.setFinishScrollingCallback(
+ finishScrollingCallback);
+ } else {
+ // It does not scroll, so call it now!
+ finishScrollingCallback.run();
+ }
+ }
+ });
}
}
};
@@ -4988,7 +5006,7 @@
}
}
- private final class DozeServiceHost extends KeyguardUpdateMonitorCallback implements DozeHost {
+ private final class DozeServiceHost implements DozeHost {
// Amount of time to allow to update the time shown on the screen before releasing
// the wakelock. This timeout is design to compensate for the fact that we don't
// currently have a way to know when time display contents have actually been
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index b73e67f..01609e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -30,11 +30,14 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.LatencyTracker;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.RemoteInputController;
import static com.android.keyguard.KeyguardHostView.OnDismissAction;
+import static com.android.systemui.statusbar.phone.FingerprintUnlockController.*;
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
@@ -323,8 +326,7 @@
}
});
} else {
- if (mFingerprintUnlockController.getMode()
- == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) {
+ if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING) {
mFingerprintUnlockController.startKeyguardFadingAway();
mPhoneStatusBar.setKeyguardFadingAway(startTime, 0, 240);
mStatusBarWindowManager.setKeyguardFadingAway(true);
@@ -341,8 +343,7 @@
boolean staying = mPhoneStatusBar.hideKeyguard();
if (!staying) {
mStatusBarWindowManager.setKeyguardFadingAway(true);
- if (mFingerprintUnlockController.getMode()
- == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
+ if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
if (!mScreenTurnedOn) {
mDeferScrimFadeOut = true;
} else {
@@ -396,6 +397,12 @@
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0);
}
}, skipFirstFrame);
+ if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
+ && LatencyTracker.isEnabled(mContext)) {
+ DejankUtils.postAfterTraversal(() ->
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK));
+ }
}
private void executeAfterKeyguardGoneAction() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 47ea59e..4425c5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -33,6 +33,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Trace;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.InputQueue;
@@ -225,6 +226,10 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN
+ && mNotificationPanel.getExpandedHeight() == 0f) {
+ mNotificationPanel.startExpandLatencyTracking();
+ }
mFalsingManager.onTouchEvent(ev, getWidth(), getHeight());
if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == VISIBLE) {
// Disallow new pointers while the brightness mirror is visible. This is so that you
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index d3ae549..f6c0942 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -381,7 +381,7 @@
}
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
- if (mIsExpanded) {
+ if (mIsExpanded || mBar.isBouncerShowing()) {
// The touchable region is always the full area when expanded
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index 3c9373b..d7920a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -123,8 +123,10 @@
mDividers.get(i).layout(0, 0, getWidth(), mDividerHeight);
}
if (mOverflowNumber != null) {
- mOverflowNumber.layout(getWidth() - mOverflowNumber.getMeasuredWidth(), 0, getWidth(),
- mOverflowNumber.getMeasuredHeight());
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ int left = (isRtl ? 0 : getWidth() - mOverflowNumber.getMeasuredWidth());
+ int right = left + mOverflowNumber.getMeasuredWidth();
+ mOverflowNumber.layout(left, 0, right, mOverflowNumber.getMeasuredHeight());
}
if (mNotificationHeader != null) {
mNotificationHeader.layout(0, 0, mNotificationHeader.getMeasuredWidth(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index c744771..38af030 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -3940,6 +3940,7 @@
private class NotificationSwipeHelper extends SwipeHelper {
private static final long SHOW_GEAR_DELAY = 60;
private static final long COVER_GEAR_DELAY = 4000;
+ private static final long SWIPE_GEAR_TIMING = 200;
private CheckForDrag mCheckForDrag;
private Runnable mFalsingCheck;
private Handler mHandler;
@@ -4056,6 +4057,9 @@
boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
+ final double timeForGesture = ev.getEventTime() - ev.getDownTime();
+ final boolean showGearForSlowOnGoing = !canChildBeDismissed(animView)
+ && timeForGesture >= SWIPE_GEAR_TIMING;
if (mGearSnappedTo && mCurrIconRow.isVisible()) {
if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
@@ -4080,7 +4084,8 @@
} else {
dismissOrSnapBack(animView, velocity, ev);
}
- } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
+ } else if (((!gestureFastEnough || showGearForSlowOnGoing)
+ && swipedEnoughToShowGear(animView))
|| gestureTowardsGear) {
// Gear has not been snapped to previously and this is gear revealing gesture
snapToGear(animView, velocity);
@@ -4132,13 +4137,9 @@
final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
final float translation = getTranslation(animView);
- final boolean fromLeft = translation > 0;
- final float absTrans = Math.abs(translation);
- final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
-
- return mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft()
- ? (translation > snapBackThreshold && translation <= notiThreshold)
- : (translation < -snapBackThreshold && translation >= -notiThreshold));
+ return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft()
+ ? translation > snapBackThreshold
+ : translation < -snapBackThreshold);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
index ca32567..cd465ac 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
@@ -36,16 +36,26 @@
import com.android.systemui.R;
import java.util.ArrayList;
+import java.util.Iterator;
+
+import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
/* Activity for choosing an application for a USB device or accessory */
public class UsbResolverActivity extends ResolverActivity {
public static final String TAG = "UsbResolverActivity";
public static final String EXTRA_RESOLVE_INFOS = "rlist";
+ public static final String EXTRA_RESOLVE_INFO = "rinfo";
private UsbDevice mDevice;
private UsbAccessory mAccessory;
private UsbDisconnectedReceiver mDisconnectedReceiver;
+ /** Resolve info that switches user profiles */
+ private ResolveInfo mForwardResolveInfo;
+
+ /** The intent that should be started when the profile is switched */
+ private Intent mOtherProfileIntent;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
Intent intent = getIntent();
@@ -56,17 +66,22 @@
return;
}
Intent target = (Intent)targetParcelable;
- ArrayList<ResolveInfo> rList = intent.getParcelableArrayListExtra(EXTRA_RESOLVE_INFOS);
- CharSequence title = getResources().getText(com.android.internal.R.string.chooseUsbActivity);
- super.onCreate(savedInstanceState, target, title, null, rList,
- true /* Set alwaysUseOption to true to enable "always use this app" checkbox. */ );
+ ArrayList<ResolveInfo> rList = new ArrayList<>(
+ intent.getParcelableArrayListExtra(EXTRA_RESOLVE_INFOS));
- CheckBox alwaysUse = (CheckBox)findViewById(com.android.internal.R.id.alwaysUse);
- if (alwaysUse != null) {
- if (mDevice == null) {
- alwaysUse.setText(R.string.always_use_accessory);
- } else {
- alwaysUse.setText(R.string.always_use_device);
+ // The rList contains the apps for all profiles of this users. Separate those. We currently
+ // only support two profiles, i.e. one forward resolve info.
+ ArrayList<ResolveInfo> rListOtherProfile = new ArrayList<>();
+ mForwardResolveInfo = null;
+ for (Iterator<ResolveInfo> iterator = rList.iterator(); iterator.hasNext();) {
+ ResolveInfo ri = iterator.next();
+
+ if (ri.getComponentInfo().name.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
+ mForwardResolveInfo = ri;
+ } else if (UserHandle.getUserId(ri.activityInfo.applicationInfo.uid)
+ != UserHandle.myUserId()) {
+ iterator.remove();
+ rListOtherProfile.add(ri);
}
}
@@ -82,6 +97,40 @@
}
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
}
+
+ // Create intent that will be used when switching to other profile. Emulate the behavior of
+ // UsbProfileGroupSettingsManager#resolveActivity
+ if (mForwardResolveInfo != null) {
+ if (rListOtherProfile.size() > 1) {
+ mOtherProfileIntent = new Intent(intent);
+ mOtherProfileIntent.putParcelableArrayListExtra(EXTRA_RESOLVE_INFOS,
+ rListOtherProfile);
+ } else {
+ mOtherProfileIntent = new Intent(this, UsbConfirmActivity.class);
+ mOtherProfileIntent.putExtra(EXTRA_RESOLVE_INFO, rListOtherProfile.get(0));
+
+ if (mDevice != null) {
+ mOtherProfileIntent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
+ }
+
+ if (mAccessory != null) {
+ mOtherProfileIntent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
+ }
+ }
+ }
+
+ CharSequence title = getResources().getText(com.android.internal.R.string.chooseUsbActivity);
+ super.onCreate(savedInstanceState, target, title, null, rList,
+ true /* Set alwaysUseOption to true to enable "always use this app" checkbox. */ );
+
+ CheckBox alwaysUse = (CheckBox)findViewById(com.android.internal.R.id.alwaysUse);
+ if (alwaysUse != null) {
+ if (mDevice == null) {
+ alwaysUse.setText(R.string.always_use_accessory);
+ } else {
+ alwaysUse.setText(R.string.always_use_device);
+ }
+ }
}
@Override
@@ -95,6 +144,12 @@
@Override
protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
final ResolveInfo ri = target.getResolveInfo();
+ if (ri == mForwardResolveInfo) {
+ startActivityAsUser(mOtherProfileIntent, null,
+ UserHandle.of(mForwardResolveInfo.targetUserId));
+ return true;
+ }
+
try {
IBinder b = ServiceManager.getService(USB_SERVICE);
IUsbManager service = IUsbManager.Stub.asInterface(b);
@@ -122,7 +177,7 @@
}
try {
- target.startAsUser(this, null, new UserHandle(userId));
+ target.startAsUser(this, null, UserHandle.of(userId));
} catch (ActivityNotFoundException e) {
Log.e(TAG, "startActivity failed", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index 047085d..8ca277e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -558,35 +558,7 @@
final VolumeRow activeRow = getActiveRow();
if (!dismissing) {
mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT);
- AutoTransition transition = new AutoTransition();
- transition.setDuration(mExpandButtonAnimationDuration);
- transition.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- transition.addListener(new Transition.TransitionListener() {
- @Override
- public void onTransitionStart(Transition transition) {
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- mWindow.setLayout(
- mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
- }
-
- @Override
- public void onTransitionCancel(Transition transition) {
- }
-
- @Override
- public void onTransitionPause(Transition transition) {
- mWindow.setLayout(
- mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
- }
-
- @Override
- public void onTransitionResume(Transition transition) {
- }
- });
- TransitionManager.beginDelayedTransition(mDialogView, transition);
+ TransitionManager.beginDelayedTransition(mDialogView, getTransistion());
}
updateRowsH(activeRow);
rescheduleTimeoutH();
@@ -702,6 +674,7 @@
final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
&& (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
&& !mZenPanel.isEditing();
+ TransitionManager.beginDelayedTransition(mDialogView, getTransistion());
if (wasVisible != visible && !visible) {
prepareForCollapse();
}
@@ -897,7 +870,7 @@
if (row.anim != null) {
row.anim.cancel();
}
- row.slider.setProgress(newProgress);
+ row.slider.setProgress(newProgress, true);
}
}
}
@@ -947,6 +920,38 @@
rescheduleTimeoutH();
}
+ private AutoTransition getTransistion() {
+ AutoTransition transition = new AutoTransition();
+ transition.setDuration(mExpandButtonAnimationDuration);
+ transition.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ transition.addListener(new Transition.TransitionListener() {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ mWindow.setLayout(
+ mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionPause(Transition transition) {
+ mWindow.setLayout(
+ mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ public void onTransitionResume(Transition transition) {
+ }
+ });
+ return transition;
+ }
+
private boolean hasTouchFeature() {
final PackageManager pm = mContext.getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 5d6ac12..23967aa 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -34,6 +34,7 @@
frameworks/base/packages/SystemUI/res \
LOCAL_STATIC_ANDROID_LIBRARIES := \
+ SystemUIPluginLib \
Keyguard \
android-support-v7-recyclerview \
android-support-v7-preference \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 869805e..d943eb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -17,13 +17,17 @@
import android.content.Context;
import android.support.test.InstrumentationRegistry;
-import android.test.AndroidTestCase;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.MessageQueue;
import org.junit.Before;
/**
* Base class that does System UI specific setup.
*/
public class SysuiTestCase {
+
+ private Handler mHandler;
protected Context mContext;
@Before
@@ -34,4 +38,65 @@
protected Context getContext() {
return mContext;
}
+
+ protected void waitForIdleSync() {
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+ waitForIdleSync(mHandler);
+ }
+
+ protected void waitForIdleSync(Handler h) {
+ validateThread(h.getLooper());
+ Idler idler = new Idler(null);
+ h.getLooper().getQueue().addIdleHandler(idler);
+ // Ensure we are non-idle, so the idle handler can run.
+ h.post(new EmptyRunnable());
+ idler.waitForIdle();
+ }
+
+ private static final void validateThread(Looper l) {
+ if (Looper.myLooper() == l) {
+ throw new RuntimeException(
+ "This method can not be called from the looper being synced");
+ }
+ }
+
+ public static final class EmptyRunnable implements Runnable {
+ public void run() {
+ }
+ }
+
+ public static final class Idler implements MessageQueue.IdleHandler {
+ private final Runnable mCallback;
+ private boolean mIdle;
+
+ public Idler(Runnable callback) {
+ mCallback = callback;
+ mIdle = false;
+ }
+
+ @Override
+ public boolean queueIdle() {
+ if (mCallback != null) {
+ mCallback.run();
+ }
+ synchronized (this) {
+ mIdle = true;
+ notifyAll();
+ }
+ return false;
+ }
+
+ public void waitForIdle() {
+ synchronized (this) {
+ while (!mIdle) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
new file mode 100644
index 0000000..ab7de39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginInstanceManager.ClassLoaderFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PluginInstanceManagerTest extends SysuiTestCase {
+
+ // Static since the plugin needs to be generated by the PluginInstanceManager using newInstance.
+ private static Plugin sMockPlugin;
+
+ private HandlerThread mHandlerThread;
+ private Context mContextWrapper;
+ private PackageManager mMockPm;
+ private PluginListener mMockListener;
+ private PluginInstanceManager mPluginInstanceManager;
+
+ @Before
+ public void setup() throws Exception {
+ mHandlerThread = new HandlerThread("test_thread");
+ mHandlerThread.start();
+ mContextWrapper = new MyContextWrapper(getContext());
+ mMockPm = mock(PackageManager.class);
+ mMockListener = mock(PluginListener.class);
+ mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
+ mMockListener, true, mHandlerThread.getLooper(), 1, true,
+ new TestClassLoaderFactory());
+ sMockPlugin = mock(Plugin.class);
+ when(sMockPlugin.getVersion()).thenReturn(1);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ sMockPlugin = null;
+ }
+
+ @Test
+ public void testNoPlugins() {
+ when(mMockPm.queryIntentServices(Mockito.any(), Mockito.anyInt())).thenReturn(
+ Collections.emptyList());
+ mPluginInstanceManager.startListening();
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+ verify(mMockListener, Mockito.never()).onPluginConnected(
+ ArgumentCaptor.forClass(Plugin.class).capture());
+ }
+
+ @Test
+ public void testPluginCreate() {
+ createPlugin();
+
+ // Verify startup lifecycle
+ verify(sMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(),
+ ArgumentCaptor.forClass(Context.class).capture());
+ verify(mMockListener).onPluginConnected(ArgumentCaptor.forClass(Plugin.class).capture());
+ }
+
+ @Test
+ public void testPluginDestroy() {
+ createPlugin(); // Get into valid created state.
+
+ mPluginInstanceManager.stopListening();
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+ // Verify shutdown lifecycle
+ verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(sMockPlugin).onDestroy();
+ }
+
+ @Test
+ public void testIncorrectVersion() {
+ setupFakePmQuery();
+ when(sMockPlugin.getVersion()).thenReturn(2);
+
+ mPluginInstanceManager.startListening();
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+ // Plugin shouldn't be connected because it is the wrong version.
+ verify(mMockListener, Mockito.never()).onPluginConnected(
+ ArgumentCaptor.forClass(Plugin.class).capture());
+ }
+
+ @Test
+ public void testReloadOnChange() {
+ createPlugin(); // Get into valid created state.
+
+ // Send a package changed broadcast.
+ Intent i = new Intent(Intent.ACTION_PACKAGE_CHANGED,
+ Uri.fromParts("package", "com.android.systemui", null));
+ mPluginInstanceManager.onReceive(mContextWrapper, i);
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+ // Verify the old one was destroyed.
+ verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(sMockPlugin).onDestroy();
+ // Also verify we got a second onCreate.
+ verify(sMockPlugin, Mockito.times(2)).onCreate(
+ ArgumentCaptor.forClass(Context.class).capture(),
+ ArgumentCaptor.forClass(Context.class).capture());
+ verify(mMockListener, Mockito.times(2)).onPluginConnected(
+ ArgumentCaptor.forClass(Plugin.class).capture());
+ }
+
+ @Test
+ public void testNonDebuggable() {
+ // Create a version that thinks the build is not debuggable.
+ mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
+ mMockListener, true, mHandlerThread.getLooper(), 1, false,
+ new TestClassLoaderFactory());
+ setupFakePmQuery();
+
+ mPluginInstanceManager.startListening();
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);;
+
+ // Non-debuggable build should receive no plugins.
+ verify(mMockListener, Mockito.never()).onPluginConnected(
+ ArgumentCaptor.forClass(Plugin.class).capture());
+ }
+
+ @Test
+ public void testCheckAndDisable() {
+ createPlugin(); // Get into valid created state.
+
+ // Start with an unrelated class.
+ boolean result = mPluginInstanceManager.checkAndDisable(Activity.class.getName());
+ assertFalse(result);
+ verify(mMockPm, Mockito.never()).setComponentEnabledSetting(
+ ArgumentCaptor.forClass(ComponentName.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture());
+
+ // Now hand it a real class and make sure it disables the plugin.
+ result = mPluginInstanceManager.checkAndDisable(TestPlugin.class.getName());
+ assertTrue(result);
+ verify(mMockPm).setComponentEnabledSetting(
+ ArgumentCaptor.forClass(ComponentName.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture());
+ }
+
+ @Test
+ public void testDisableAll() {
+ createPlugin(); // Get into valid created state.
+
+ mPluginInstanceManager.disableAll();
+
+ verify(mMockPm).setComponentEnabledSetting(
+ ArgumentCaptor.forClass(ComponentName.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture());
+ }
+
+ private void setupFakePmQuery() {
+ List<ResolveInfo> list = new ArrayList<>();
+ ResolveInfo info = new ResolveInfo();
+ info.serviceInfo = new ServiceInfo();
+ info.serviceInfo.packageName = "com.android.systemui";
+ info.serviceInfo.name = TestPlugin.class.getName();
+ list.add(info);
+ when(mMockPm.queryIntentServices(Mockito.any(), Mockito.anyInt())).thenReturn(list);
+
+ when(mMockPm.checkPermission(Mockito.anyString(), Mockito.anyString())).thenReturn(
+ PackageManager.PERMISSION_GRANTED);
+
+ try {
+ ApplicationInfo appInfo = getContext().getApplicationInfo();
+ when(mMockPm.getApplicationInfo(Mockito.anyString(), Mockito.anyInt())).thenReturn(
+ appInfo);
+ } catch (NameNotFoundException e) {
+ // Shouldn't be possible, but if it is, we want to fail.
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void createPlugin() {
+ setupFakePmQuery();
+
+ mPluginInstanceManager.startListening();
+
+ waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+ waitForIdleSync(mPluginInstanceManager.mMainHandler);
+ }
+
+ private static class TestClassLoaderFactory extends ClassLoaderFactory {
+ @Override
+ public ClassLoader createClassLoader(String path, ClassLoader base) {
+ return base;
+ }
+ }
+
+ // Real context with no registering/unregistering of receivers.
+ private static class MyContextWrapper extends ContextWrapper {
+ public MyContextWrapper(Context base) {
+ super(base);
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ return null;
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ }
+ }
+
+ public static class TestPlugin implements Plugin {
+ @Override
+ public int getVersion() {
+ return sMockPlugin.getVersion();
+ }
+
+ @Override
+ public void onCreate(Context sysuiContext, Context pluginContext) {
+ sMockPlugin.onCreate(sysuiContext, pluginContext);
+ }
+
+ @Override
+ public void onDestroy() {
+ sMockPlugin.onDestroy();
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
new file mode 100644
index 0000000..56e742a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginManager.PluginInstanceManagerFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PluginManagerTest extends SysuiTestCase {
+
+ private PluginInstanceManagerFactory mMockFactory;
+ private PluginInstanceManager mMockPluginInstance;
+ private PluginManager mPluginManager;
+ private PluginListener mMockListener;
+
+ private UncaughtExceptionHandler mRealExceptionHandler;
+ private UncaughtExceptionHandler mMockExceptionHandler;
+ private UncaughtExceptionHandler mPluginExceptionHandler;
+
+ @Before
+ public void setup() throws Exception {
+ mRealExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
+ mMockExceptionHandler = mock(UncaughtExceptionHandler.class);
+ mMockFactory = mock(PluginInstanceManagerFactory.class);
+ mMockPluginInstance = mock(PluginInstanceManager.class);
+ when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(),
+ Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt()))
+ .thenReturn(mMockPluginInstance);
+ mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler);
+ resetExceptionHandler();
+ mMockListener = mock(PluginListener.class);
+ }
+
+ @Test
+ public void testAddListener() {
+ mPluginManager.addPluginListener("myAction", mMockListener, 1);
+
+ verify(mMockPluginInstance).startListening();
+ }
+
+ @Test
+ public void testRemoveListener() {
+ mPluginManager.addPluginListener("myAction", mMockListener, 1);
+
+ mPluginManager.removePluginListener(mMockListener);
+ verify(mMockPluginInstance).stopListening();
+ }
+
+ @Test
+ public void testNonDebuggable() {
+ mPluginManager = new PluginManager(getContext(), mMockFactory, false,
+ mMockExceptionHandler);
+ resetExceptionHandler();
+ mPluginManager.addPluginListener("myAction", mMockListener, 1);
+
+ verify(mMockPluginInstance, Mockito.never()).startListening();
+ }
+
+ @Test
+ public void testExceptionHandler_foundPlugin() {
+ mPluginManager.addPluginListener("myAction", mMockListener, 1);
+ when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(true);
+
+ mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());
+
+ verify(mMockPluginInstance, Mockito.atLeastOnce()).checkAndDisable(
+ ArgumentCaptor.forClass(String.class).capture());
+ verify(mMockPluginInstance, Mockito.never()).disableAll();
+ verify(mMockExceptionHandler).uncaughtException(
+ ArgumentCaptor.forClass(Thread.class).capture(),
+ ArgumentCaptor.forClass(Throwable.class).capture());
+ }
+
+ @Test
+ public void testExceptionHandler_noFoundPlugin() {
+ mPluginManager.addPluginListener("myAction", mMockListener, 1);
+ when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(false);
+
+ mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());
+
+ verify(mMockPluginInstance, Mockito.atLeastOnce()).checkAndDisable(
+ ArgumentCaptor.forClass(String.class).capture());
+ verify(mMockPluginInstance).disableAll();
+ verify(mMockExceptionHandler).uncaughtException(
+ ArgumentCaptor.forClass(Thread.class).capture(),
+ ArgumentCaptor.forClass(Throwable.class).capture());
+ }
+
+ private void resetExceptionHandler() {
+ mPluginExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
+ // Set back the real exception handler so the test can crash if it wants to.
+ Thread.setDefaultUncaughtExceptionHandler(mRealExceptionHandler);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
new file mode 100644
index 0000000..39b6412
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.power;
+
+import static android.test.MoreAsserts.assertNotEqual;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PowerNotificationWarningsTest {
+ private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
+ private PowerNotificationWarnings mPowerNotificationWarnings;
+
+ @Before
+ public void setUp() throws Exception {
+ // Test Instance.
+ mPowerNotificationWarnings = new PowerNotificationWarnings(
+ InstrumentationRegistry.getTargetContext(), mMockNotificationManager, null);
+ }
+
+ @Test
+ public void testIsInvalidChargerWarningShowing_DefaultsToFalse() {
+ assertFalse(mPowerNotificationWarnings.isInvalidChargerWarningShowing());
+ }
+
+ @Test
+ public void testIsInvalidChargerWarningShowing_TrueAfterShow() {
+ mPowerNotificationWarnings.showInvalidChargerWarning();
+ assertTrue(mPowerNotificationWarnings.isInvalidChargerWarningShowing());
+ }
+
+ @Test
+ public void testIsInvalidChargerWarningShowing_FalseAfterDismiss() {
+ mPowerNotificationWarnings.showInvalidChargerWarning();
+ mPowerNotificationWarnings.dismissInvalidChargerWarning();
+ assertFalse(mPowerNotificationWarnings.isInvalidChargerWarningShowing());
+ }
+
+ @Test
+ public void testShowInvalidChargerNotification_NotifyAsUser() {
+ mPowerNotificationWarnings.showInvalidChargerWarning();
+ verify(mMockNotificationManager, times(1))
+ .notifyAsUser(anyString(), anyInt(), any(), any());
+ }
+
+ @Test
+ public void testDismissInvalidChargerNotification_CancelAsUser() {
+ mPowerNotificationWarnings.showInvalidChargerWarning();
+ mPowerNotificationWarnings.dismissInvalidChargerWarning();
+ verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(), anyInt(), any());
+ }
+
+ @Test
+ public void testShowLowBatteryNotification_NotifyAsUser() {
+ mPowerNotificationWarnings.showLowBatteryWarning(false);
+ verify(mMockNotificationManager, times(1))
+ .notifyAsUser(anyString(), anyInt(), any(), any());
+ }
+
+ @Test
+ public void testDismissLowBatteryNotification_CancelAsUser() {
+ mPowerNotificationWarnings.showLowBatteryWarning(false);
+ mPowerNotificationWarnings.dismissLowBatteryWarning();
+ verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(), anyInt(), any());
+ }
+
+ @Test
+ public void testShowLowBatteryNotification_Silent() {
+ mPowerNotificationWarnings.showLowBatteryWarning(false);
+ ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
+ verify(mMockNotificationManager)
+ .notifyAsUser(anyString(), anyInt(), captor.capture(), any());
+ assertEquals(null, captor.getValue().sound);
+ }
+
+ @Test
+ public void testShowLowBatteryNotification_Sound() {
+ mPowerNotificationWarnings.showLowBatteryWarning(true);
+ ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
+ verify(mMockNotificationManager)
+ .notifyAsUser(anyString(), anyInt(), captor.capture(), any());
+ assertNotEqual(null, captor.getValue().sound);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 2bfbc5f..d7ff04f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -15,12 +15,14 @@
*/
package com.android.systemui.qs.external;
-import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Service;
@@ -29,6 +31,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.ServiceInfo;
import android.net.Uri;
@@ -43,55 +46,74 @@
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
+import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
import android.util.Log;
-import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class TileLifecycleManagerTest extends SysuiTestCase {
- public static final String TILE_UPDATE_BROADCAST = "com.android.systemui.tests.TILE_UPDATE";
- public static final String EXTRA_CALLBACK = "callback";
+public class TileLifecycleManagerTest {
+ private static final int TEST_FAIL_TIMEOUT = 5000;
+ private final Context mMockContext = Mockito.mock(Context.class);
+ private final PackageManagerAdapter mMockPackageManagerAdapter =
+ Mockito.mock(PackageManagerAdapter.class);
+ private final IQSTileService.Stub mMockTileService = Mockito.mock(IQSTileService.Stub.class);
+ private ComponentName mTileServiceComponentName;
+ private Intent mTileServiceIntent;
+ private UserHandle mUser;
private HandlerThread mThread;
private Handler mHandler;
private TileLifecycleManager mStateManager;
- private final Object mBroadcastLock = new Object();
- private final ArraySet<String> mCallbacks = new ArraySet<>();
- private final PackageManagerAdapter mMockPackageManagerAdapter =
- Mockito.mock(PackageManagerAdapter.class);
- private boolean mBound;
@Before
public void setUp() throws Exception {
setPackageEnabled(true);
+ mTileServiceComponentName = new ComponentName(
+ InstrumentationRegistry.getTargetContext(), "FakeTileService.class");
+
+ // Stub.asInterface will just return itself.
+ when(mMockTileService.queryLocalInterface(anyString())).thenReturn(mMockTileService);
+
+ // Default behavior for bind is success and connects as mMockTileService.
+ when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any()))
+ .thenAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) {
+ ServiceConnection connection =
+ (ServiceConnection) invocation.getArguments()[1];
+ connection.onServiceConnected(
+ mTileServiceComponentName, mMockTileService);
+ return true;
+ }
+ });
+
+
+ mTileServiceIntent = new Intent().setComponent(mTileServiceComponentName);
+ mUser = new UserHandle(UserHandle.myUserId());
mThread = new HandlerThread("TestThread");
mThread.start();
mHandler = new Handler(mThread.getLooper());
- ComponentName component = new ComponentName(getContext(), FakeTileService.class);
- mStateManager = new TileLifecycleManager(mHandler, getContext(),
+ mStateManager = new TileLifecycleManager(mHandler, mMockContext,
Mockito.mock(IQSService.class), new Tile(),
- new Intent().setComponent(component),
- new UserHandle(UserHandle.myUserId()),
+ mTileServiceIntent,
+ mUser,
mMockPackageManagerAdapter);
- mCallbacks.clear();
- getContext().registerReceiver(mReceiver, new IntentFilter(TILE_UPDATE_BROADCAST));
}
@After
public void tearDown() throws Exception {
- if (mBound) {
- unbindService();
- }
mThread.quit();
- getContext().unregisterReceiver(mReceiver);
}
private void setPackageEnabled(boolean enabled) throws Exception {
@@ -110,260 +132,134 @@
.thenReturn(defaultPackageInfo);
}
- @Test
- public void testSync() {
- syncWithHandler();
+ private void verifyBind(int times) {
+ verify(mMockContext, times(times)).bindServiceAsUser(
+ mTileServiceIntent,
+ mStateManager,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+ mUser);
}
@Test
public void testBind() {
- bindService();
- waitForCallback("onCreate");
+ mStateManager.setBindService(true);
+ verifyBind(1);
}
@Test
public void testUnbind() {
- bindService();
- waitForCallback("onCreate");
- unbindService();
- waitForCallback("onDestroy");
+ mStateManager.setBindService(true);
+ mStateManager.setBindService(false);
+ verify(mMockContext).unbindService(mStateManager);
}
@Test
- public void testTileServiceCallbacks() {
- bindService();
- waitForCallback("onCreate");
-
+ public void testTileServiceCallbacks() throws Exception {
+ mStateManager.setBindService(true);
mStateManager.onTileAdded();
- waitForCallback("onTileAdded");
+ verify(mMockTileService).onTileAdded();
mStateManager.onStartListening();
- waitForCallback("onStartListening");
+ verify(mMockTileService).onStartListening();
mStateManager.onClick(null);
- waitForCallback("onClick");
+ verify(mMockTileService).onClick(null);
mStateManager.onStopListening();
- waitForCallback("onStopListening");
+ verify(mMockTileService).onStopListening();
mStateManager.onTileRemoved();
- waitForCallback("onTileRemoved");
-
- unbindService();
+ verify(mMockTileService).onTileRemoved();
}
@Test
- public void testAddedBeforeBind() {
+ public void testAddedBeforeBind() throws Exception {
mStateManager.onTileAdded();
+ mStateManager.setBindService(true);
- bindService();
- waitForCallback("onCreate");
- waitForCallback("onTileAdded");
+ verifyBind(1);
+ verify(mMockTileService).onTileAdded();
}
@Test
- public void testListeningBeforeBind() {
+ public void testListeningBeforeBind() throws Exception {
mStateManager.onTileAdded();
mStateManager.onStartListening();
+ mStateManager.setBindService(true);
- bindService();
- waitForCallback("onCreate");
- waitForCallback("onTileAdded");
- waitForCallback("onStartListening");
+ verifyBind(1);
+ verify(mMockTileService).onTileAdded();
+ verify(mMockTileService).onStartListening();
}
@Test
- public void testClickBeforeBind() {
+ public void testClickBeforeBind() throws Exception {
mStateManager.onTileAdded();
mStateManager.onStartListening();
mStateManager.onClick(null);
+ mStateManager.setBindService(true);
- bindService();
- waitForCallback("onCreate");
- waitForCallback("onTileAdded");
- waitForCallback("onStartListening");
- waitForCallback("onClick");
+ verifyBind(1);
+ verify(mMockTileService).onTileAdded();
+ verify(mMockTileService).onStartListening();
+ verify(mMockTileService).onClick(null);
}
@Test
- public void testListeningNotListeningBeforeBind() {
+ public void testListeningNotListeningBeforeBind() throws Exception {
mStateManager.onTileAdded();
mStateManager.onStartListening();
mStateManager.onStopListening();
+ mStateManager.setBindService(true);
- bindService();
- waitForCallback("onCreate");
- unbindService();
- waitForCallback("onDestroy");
- assertFalse(mCallbacks.contains("onStartListening"));
+ verifyBind(1);
+ mStateManager.setBindService(false);
+ verify(mMockContext).unbindService(mStateManager);
+ verify(mMockTileService, never()).onStartListening();
}
@Test
- public void testNoClickOfNotListeningAnymore() {
+ public void testNoClickOfNotListeningAnymore() throws Exception {
mStateManager.onTileAdded();
mStateManager.onStartListening();
mStateManager.onClick(null);
mStateManager.onStopListening();
+ mStateManager.setBindService(true);
- bindService();
- waitForCallback("onCreate");
- unbindService();
- waitForCallback("onDestroy");
- assertFalse(mCallbacks.contains("onClick"));
+ verifyBind(1);
+ mStateManager.setBindService(false);
+ verify(mMockContext).unbindService(mStateManager);
+ verify(mMockTileService, never()).onClick(null);
}
@Test
public void testComponentEnabling() throws Exception {
mStateManager.onTileAdded();
mStateManager.onStartListening();
-
setPackageEnabled(false);
- bindService();
- // Package not available, should be listening for package changes.
- assertTrue(mStateManager.mReceiverRegistered);
+ mStateManager.setBindService(true);
+ // Package not available, not yet created.
+ verifyBind(0);
// Package is re-enabled.
setPackageEnabled(true);
mStateManager.onReceive(
- mContext,
+ mMockContext,
new Intent(
Intent.ACTION_PACKAGE_CHANGED,
- Uri.fromParts("package", getContext().getPackageName(), null)));
- waitForCallback("onCreate");
+ Uri.fromParts(
+ "package", mTileServiceComponentName.getPackageName(), null)));
+ verifyBind(1);
}
@Test
- public void testKillProcess() {
+ public void testKillProcess() throws Exception {
mStateManager.onStartListening();
- bindService();
- waitForCallback("onCreate");
- waitForCallback("onStartListening");
-
- getContext().sendBroadcast(new Intent(FakeTileService.ACTION_KILL));
-
- waitForCallback("onCreate");
- waitForCallback("onStartListening");
- }
-
- private void bindService() {
- mBound = true;
mStateManager.setBindService(true);
- }
+ mStateManager.setBindRetryDelay(0);
+ mStateManager.onServiceDisconnected(mTileServiceComponentName);
- private void unbindService() {
- mBound = false;
- mStateManager.setBindService(false);
- }
+ // Guarantees mHandler has processed all messages.
+ assertTrue(mHandler.runWithScissors(()->{}, TEST_FAIL_TIMEOUT));
- private void waitForCallback(String callback) {
- for (int i = 0; i < 50; i++) {
- if (mCallbacks.contains(callback)) {
- mCallbacks.remove(callback);
- return;
- }
- synchronized (mBroadcastLock) {
- try {
- mBroadcastLock.wait(500);
- } catch (InterruptedException e) {
- }
- }
- }
- if (mCallbacks.contains(callback)) {
- mCallbacks.remove(callback);
- return;
- }
- fail("Didn't receive callback: " + callback);
- }
-
- private void syncWithHandler() {
- final Object lock = new Object();
- synchronized (lock) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- synchronized (lock) {
- lock.notify();
- }
- }
- });
- try {
- lock.wait(10000);
- } catch (InterruptedException e) {
- }
- }
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mCallbacks.add(intent.getStringExtra(EXTRA_CALLBACK));
- synchronized (mBroadcastLock) {
- mBroadcastLock.notify();
- }
- }
- };
-
- public static class FakeTileService extends Service {
- public static final String ACTION_KILL = "com.android.systemui.test.KILL";
-
- @Override
- public IBinder onBind(Intent intent) {
- return new IQSTileService.Stub() {
- @Override
- public void onTileAdded() throws RemoteException {
- sendCallback("onTileAdded");
- }
-
- @Override
- public void onTileRemoved() throws RemoteException {
- sendCallback("onTileRemoved");
- }
-
- @Override
- public void onStartListening() throws RemoteException {
- sendCallback("onStartListening");
- }
-
- @Override
- public void onStopListening() throws RemoteException {
- sendCallback("onStopListening");
- }
-
- @Override
- public void onClick(IBinder iBinder) throws RemoteException {
- sendCallback("onClick");
- }
-
- @Override
- public void onUnlockComplete() throws RemoteException {
- sendCallback("onUnlockComplete");
- }
- };
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- registerReceiver(mReceiver, new IntentFilter(ACTION_KILL));
- sendCallback("onCreate");
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- unregisterReceiver(mReceiver);
- sendCallback("onDestroy");
- }
-
- private void sendCallback(String callback) {
- Log.d("TileLifecycleManager", "Relaying: " + callback);
- sendBroadcast(new Intent(TILE_UPDATE_BROADCAST)
- .putExtra(EXTRA_CALLBACK, callback));
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_KILL.equals(intent.getAction())) {
- Process.killProcess(Process.myPid());
- }
- }
- };
+ // Two calls: one for the first bind, one for the restart.
+ verifyBind(2);
+ verify(mMockTileService, times(2)).onStartListening();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/RecentsTaskLoadPlanTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/RecentsTaskLoadPlanTest.java
deleted file mode 100644
index 4280ff4..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/RecentsTaskLoadPlanTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import android.app.ActivityManager;
-import android.support.test.runner.AndroidJUnit4;
-import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-
-import java.util.ArrayList;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-
-
-/**
- * Mock task loader that does not actually load any tasks.
- */
-class MockRecentsTaskNonLoader extends RecentsTaskLoader {
- @Override
- public String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
- return "";
- }
-
- @Override
- public String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) {
- return "";
- }
-
- @Override
- public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td, Resources res, boolean loadIfNotCached) {
- return null;
- }
-
- @Override
- public Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
- return null;
- }
-
- @Override
- public int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
- return 0;
- }
-
- @Override
- public int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
- return 0;
- }
-
- @Override
- public ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
- return null;
- }
-}
-
-/**
- * TODO(winsonc):
- * - add test to ensure excluded tasks are loaded at the front of the list
- * - add test to ensure the last visible task active time is migrated from absolute to uptime
- */
-@RunWith(AndroidJUnit4.class)
-public class RecentsTaskLoadPlanTest extends SysuiTestCase {
- private static final String TAG = "RecentsTaskLoadPlanTest";
-
- private MockRecentsTaskNonLoader mDummyLoader = new MockRecentsTaskNonLoader();
- private SystemServicesProxy mDummySsp = new SystemServicesProxy();
-
- @Test
- public void testEmptyRecents() {
- RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(mContext, mDummySsp);
- ArrayList<ActivityManager.RecentTaskInfo> tasks = new ArrayList<>();
- loadPlan.setInternals(tasks, 0 /* current */, 0 /* lastVisibleTaskActive */);
- loadPlan.preloadPlan(mDummyLoader, 0 /* runningTaskId */,
- false /* includeFrontMostExcludedTask */);
- assertFalse("Expected task to be empty", loadPlan.getTaskStack().getStackTaskCount() > 0);
- }
-
- @Test
- public void testLessThanEqualMinTasks() {
- RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(mContext, mDummySsp);
- ArrayList<ActivityManager.RecentTaskInfo> tasks = new ArrayList<>();
- int minTasks = 3;
-
- resetTaskInfoList(tasks,
- createTaskInfo(0, 1),
- createTaskInfo(1, 2),
- createTaskInfo(2, 3));
-
- // Ensure that all tasks are loaded if the tasks are within the session and after the last
- // visible active time (all tasks are loaded because there are < minTasks number of tasks)
- loadPlan.setInternals(tasks, minTasks, 0 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
-
- loadPlan.setInternals(tasks, minTasks, 1 /* current */, 0 /* lastVisibleTaskActive */,
- 0 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
-
- loadPlan.setInternals(tasks, minTasks, 1 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
-
- loadPlan.setInternals(tasks, minTasks, 3 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
-
- loadPlan.setInternals(tasks, minTasks, 3 /* current */, 1 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
-
- loadPlan.setInternals(tasks, minTasks, 50 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
-
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
-
- // Ensure that only tasks are not loaded if are after the last visible active time, even if
- // they are within the session
- loadPlan.setInternals(tasks, minTasks, 50 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
-
- loadPlan.setInternals(tasks, minTasks, 50 /* current */, 1 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
-
- loadPlan.setInternals(tasks, minTasks, 50 /* current */, 2 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0);
- assertTasksInStack(loadPlan.getTaskStack(), 1, 2);
-
- loadPlan.setInternals(tasks, minTasks, 50 /* current */, 3 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
- assertTasksInStack(loadPlan.getTaskStack(), 2);
-
- loadPlan.setInternals(tasks, minTasks, 50 /* current */, 50 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2);
- }
-
- @Test
- public void testMoreThanMinTasks() {
- RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(mContext, mDummySsp);
- ArrayList<ActivityManager.RecentTaskInfo> tasks = new ArrayList<>();
- int minTasks = 3;
-
- // Create all tasks within the session
- resetTaskInfoList(tasks,
- createTaskInfo(0, 1),
- createTaskInfo(1, 50),
- createTaskInfo(2, 100),
- createTaskInfo(3, 101),
- createTaskInfo(4, 102),
- createTaskInfo(5, 103));
-
- // Ensure that only the tasks that are within the window but after the last visible active
- // time is loaded, or the minTasks number of tasks are loaded if there are less than that
-
- // Session window shifts
- loadPlan.setInternals(tasks, minTasks, 0 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 1 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 51 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 52 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0);
- assertTasksInStack(loadPlan.getTaskStack(), 1, 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 100 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0);
- assertTasksInStack(loadPlan.getTaskStack(), 1, 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 101 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
- assertTasksInStack(loadPlan.getTaskStack(), 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 103 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
- assertTasksInStack(loadPlan.getTaskStack(), 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
- assertTasksInStack(loadPlan.getTaskStack(), 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 151 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2);
- assertTasksInStack(loadPlan.getTaskStack(), 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 200 /* current */, 0 /* lastVisibleTaskActive */,
- 50 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2);
- assertTasksInStack(loadPlan.getTaskStack(), 3, 4, 5);
-
- // Last visible active time shifts (everything is in window)
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 0 /* lastVisibleTaskActive */,
- 150 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 1 /* lastVisibleTaskActive */,
- 150 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 2 /* lastVisibleTaskActive */,
- 150 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0);
- assertTasksInStack(loadPlan.getTaskStack(), 1, 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 50 /* lastVisibleTaskActive */,
- 150 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0);
- assertTasksInStack(loadPlan.getTaskStack(), 1, 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 51 /* lastVisibleTaskActive */,
- 150 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
- assertTasksInStack(loadPlan.getTaskStack(), 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 100 /* lastVisibleTaskActive */,
- 150 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
- assertTasksInStack(loadPlan.getTaskStack(), 2, 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 101 /* lastVisibleTaskActive */,
- 150 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2);
- assertTasksInStack(loadPlan.getTaskStack(), 3, 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 102 /* lastVisibleTaskActive */,
- 150 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2, 3);
- assertTasksInStack(loadPlan.getTaskStack(), 4, 5);
-
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 103 /* lastVisibleTaskActive */,
- 150 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4);
- assertTasksInStack(loadPlan.getTaskStack(), 5);
-
- loadPlan.setInternals(tasks, minTasks, 150 /* current */, 104 /* lastVisibleTaskActive */,
- 150 /* sessionBegin */);
- loadPlan.preloadPlan(mDummyLoader, 0, false);
- assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
- }
-
- private ActivityManager.RecentTaskInfo createTaskInfo(int taskId, long lastActiveTime) {
- ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo();
- info.id = info.persistentId = taskId;
- info.lastActiveTime = lastActiveTime;
- return info;
- }
-
- private void resetTaskInfoList(ArrayList<ActivityManager.RecentTaskInfo> tasks,
- ActivityManager.RecentTaskInfo ... infos) {
- tasks.clear();
- for (ActivityManager.RecentTaskInfo info : infos) {
- tasks.add(info);
- }
- }
-
- private void assertTasksInStack(TaskStack stack, int... taskIds) {
- for (int taskId : taskIds) {
- assertNotNull("Expected task " + taskId + " in stack", stack.findTaskWithId(taskId));
- }
- }
-
- private void assertTasksNotInStack(TaskStack stack, int... taskIds) {
- for (int taskId : taskIds) {
- assertNull("Expected task " + taskId + " not in stack", stack.findTaskWithId(taskId));
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotTest.java
deleted file mode 100644
index 04e8915..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.screenshot;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Environment;
-import android.os.FileObserver;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.view.KeyEvent;
-import com.android.systemui.screenshot.ScreenshotStubActivity;
-import java.io.File;
-import org.junit.Rule;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.assertNotNull;
-
-/**
- * Functional tests for the global screenshot feature.
- */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class ScreenshotTest {
-
- private static final String LOG_TAG = "ScreenshotTest";
- private static final int SCREEN_WAIT_TIME_SEC = 5;
-
- public ScreenshotTest() {}
-
- @Rule
- public ActivityTestRule<ScreenshotStubActivity> mActivityRule =
- new ActivityTestRule<>(ScreenshotStubActivity.class);
-
- /**
- * A simple test for screenshots that launches an Activity, injects the key event combo
- * to trigger the screenshot, and verifies the screenshot was taken successfully.
- */
- @Test
- public void testScreenshot() throws Exception {
- if (true) {
- // Disable until this works again.
- return;
- }
- Log.d(LOG_TAG, "starting testScreenshot");
- // launch the activity.
- ScreenshotStubActivity activity = mActivityRule.getActivity();
- assertNotNull(activity);
-
- File screenshotDir = getScreenshotDir();
- NewScreenshotObserver observer = new NewScreenshotObserver(
- screenshotDir.getAbsolutePath());
- observer.startWatching();
- takeScreenshot();
- // unlikely, but check if a new screenshot file was already created
- if (observer.getCreatedPath() == null) {
- // wait for screenshot to be created
- synchronized(observer) {
- observer.wait(SCREEN_WAIT_TIME_SEC*1000);
- }
- }
- assertNotNull(String.format("Could not find screenshot after %d seconds",
- SCREEN_WAIT_TIME_SEC), observer.getCreatedPath());
-
- File screenshotFile = new File(screenshotDir, observer.getCreatedPath());
- try {
- assertTrue(String.format("Detected new screenshot %s but its not a file",
- screenshotFile.getName()), screenshotFile.isFile());
- assertTrue(String.format("Detected new screenshot %s but its not an image",
- screenshotFile.getName()), isValidImage(screenshotFile));
- } finally {
- // delete the file to prevent external storage from filing up
- screenshotFile.delete();
- }
- }
-
- private static class NewScreenshotObserver extends FileObserver {
- private String mAddedPath = null;
-
- NewScreenshotObserver(String path) {
- super(path, FileObserver.CREATE);
- }
-
- synchronized String getCreatedPath() {
- return mAddedPath;
- }
-
- @Override
- public void onEvent(int event, String path) {
- Log.d(LOG_TAG, String.format("Detected new file added %s", path));
- synchronized (this) {
- mAddedPath = path;
- notify();
- }
- }
- }
-
- /**
- * Inject the key sequence to take a screenshot.
- */
- private void takeScreenshot() {
- InstrumentationRegistry.getInstrumentation()
- .sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_POWER));
- InstrumentationRegistry.getInstrumentation()
- .sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN));
- // the volume down key event will cause the 'volume adjustment' UI to appear in the
- // foreground, and steal UI focus
- // unfortunately this means the next key event will get directed to the
- // 'volume adjustment' UI, instead of this test's activity
- // for this reason this test must be signed with platform certificate, to grant this test
- // permission to inject key events to another process
- InstrumentationRegistry.getInstrumentation()
- .sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_DOWN));
- InstrumentationRegistry.getInstrumentation()
- .sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_POWER));
- }
-
- /**
- * Get the directory where screenshot images are stored.
- */
- private File getScreenshotDir() {
- // TODO: get this dir location from a constant
- return new File(Environment.getExternalStorageDirectory(), "Pictures" + File.separator +
- "Screenshots");
- }
-
- /**
- * Return true if file is valid image file
- */
- private boolean isValidImage(File screenshotFile) {
- Bitmap b = BitmapFactory.decodeFile(screenshotFile.getAbsolutePath());
- // TODO: do more checks on image
- return b != null;
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index c51cef0..6c9cfe0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -37,6 +37,9 @@
import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@@ -76,6 +79,19 @@
private NetworkCapabilities mNetCapabilities;
+ @Rule
+ public TestWatcher failWatcher = new TestWatcher() {
+ @Override
+ protected void failed(Throwable e, Description description) {
+ // Print out mNetworkController state if the test fails.
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ mNetworkController.dump(null, pw, null);
+ pw.flush();
+ Log.d(TAG, sw.toString());
+ }
+ };
+
@Before
public void setUp() throws Exception {
mMockWm = mock(WifiManager.class);
@@ -147,15 +163,6 @@
}
- @After
- public void tearDown() throws Exception {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- mNetworkController.dump(null, pw, null);
- pw.flush();
- Log.d(TAG, sw.toString());
- }
-
// 2 Bars 3G GSM.
public void setupDefaultSignal() {
setIsGsm(true);
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 00707e4..e836a2a 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -2279,6 +2279,60 @@
// OPEN: Settings > Security > Device administrators
DEVICE_ADMIN_SETTINGS = 516;
+ // ACTION: Managed provisioning was launched to set this package as DPC app.
+ // PACKAGE: DPC's package name.
+ PROVISIONING_DPC_PACKAGE_NAME = 517;
+
+ // ACTION: Managed provisioning triggered DPC installation.
+ // PACKAGE: Package name of package which installed DPC.
+ PROVISIONING_DPC_INSTALLED_BY_PACKAGE = 518;
+
+ // ACTION: Logged when provisioning activity finishes.
+ // TIME: Indicates time taken by provisioning activity to finish in MS.
+ PROVISIONING_PROVISIONING_ACTIVITY_TIME_MS = 519;
+
+ // ACTION: Logged when preprovisioning activity finishes.
+ // TIME: Indicates time taken by preprovisioning activity to finish in MS.
+ PROVISIONING_PREPROVISIONING_ACTIVITY_TIME_MS = 520;
+
+ // ACTION: Logged when encrypt device activity finishes.
+ // TIME: Indicates time taken by encrypt device activity to finish in MS.
+ PROVISIONING_ENCRYPT_DEVICE_ACTIVITY_TIME_MS = 521;
+
+ // ACTION: Logged when web activity finishes.
+ // TIME: Indicates total time taken by web activity to finish in MS.
+ PROVISIONING_WEB_ACTIVITY_TIME_MS = 522;
+
+ // ACTION: Logged when trampoline activity finishes.
+ // TIME: Indicates total time taken by trampoline activity to finish in MS.
+ PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 523;
+
+ // ACTION: Logged when encryption activity finishes.
+ // TIME: Indicates total time taken by post encryption activity to finish in MS.
+ PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 524;
+
+ // ACTION: Logged when finalization activity finishes.
+ // TIME: Indicates time taken by finalization activity to finish in MS.
+ PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 525;
+
+ // OPEN: Settings Support > Phone/Chat -> Disclaimer
+ DIALOG_SUPPORT_DISCLAIMER = 526;
+
+ // OPEN: Settings Support > Travel abroad
+ DIALOG_SUPPORT_PHONE = 527;
+
+ // OPEN: Settings > Security > Factory Reset Protection dialog
+ DIALOG_FRP = 528;
+
+ // OPEN: Settings > Custom list preference with confirmation message
+ DIALOG_CUSTOM_LIST_CONFIRMATION = 529;
+
+ // OPEN: Settings > APN Editor > Error dialog
+ DIALOG_APN_EDITOR_ERROR = 530;
+
+ // OPEN: Settings > Users > Edit owner info dialog
+ DIALOG_OWNER_INFO_SETTINGS = 531;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 32adc20..0b83e66 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1315,8 +1315,14 @@
private void updateServicesLocked(UserState userState) {
Map<ComponentName, Service> componentNameToServiceMap =
userState.mComponentNameToServiceMap;
- boolean isUnlockingOrUnlocked = mContext.getSystemService(UserManager.class)
- .isUserUnlockingOrUnlocked(userState.mUserId);
+ boolean isUnlockingOrUnlocked;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ isUnlockingOrUnlocked = mContext.getSystemService(UserManager.class)
+ .isUserUnlockingOrUnlocked(userState.mUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
@@ -2531,7 +2537,7 @@
// share the accessibility state of the parent. The call below
// performs the current profile parent resolution.
final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(UserHandle.USER_CURRENT);
+ .resolveCallingUserIdEnforcingPermissionsLocked(UserHandle.getCallingUserId());
return resolvedUserId == mCurrentUserId;
}
@@ -3733,13 +3739,6 @@
Rect boundsInScreen = mTempRect;
focus.getBoundsInScreen(boundsInScreen);
- // Clip to the window bounds.
- Rect windowBounds = mTempRect1;
- getWindowBounds(focus.getWindowId(), windowBounds);
- if (!boundsInScreen.intersect(windowBounds)) {
- return false;
- }
-
// Apply magnification if needed.
MagnificationSpec spec = getCompatibleMagnificationSpecLocked(focus.getWindowId());
if (spec != null && !spec.isNop()) {
@@ -3747,6 +3746,13 @@
boundsInScreen.scale(1 / spec.scale);
}
+ // Clip to the window bounds.
+ Rect windowBounds = mTempRect1;
+ getWindowBounds(focus.getWindowId(), windowBounds);
+ if (!boundsInScreen.intersect(windowBounds)) {
+ return false;
+ }
+
// Clip to the screen bounds.
Point screenSize = mTempPoint;
mDefaultDisplay.getRealSize(screenSize);
diff --git a/services/accessibility/java/com/android/server/accessibility/DisplayAdjustmentUtils.java b/services/accessibility/java/com/android/server/accessibility/DisplayAdjustmentUtils.java
index 1532946..c81a876 100644
--- a/services/accessibility/java/com/android/server/accessibility/DisplayAdjustmentUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/DisplayAdjustmentUtils.java
@@ -18,6 +18,7 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.os.Binder;
import android.provider.Settings.Secure;
import android.view.accessibility.AccessibilityManager;
@@ -60,10 +61,15 @@
final DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
int daltonizerMode = AccessibilityManager.DALTONIZER_DISABLED;
- if (Secure.getIntForUser(cr,
- Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, userId) != 0) {
- daltonizerMode = Secure.getIntForUser(cr,
- Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, DEFAULT_DISPLAY_DALTONIZER, userId);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ if (Secure.getIntForUser(cr,
+ Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, userId) != 0) {
+ daltonizerMode = Secure.getIntForUser(cr,
+ Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, DEFAULT_DISPLAY_DALTONIZER, userId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
float[] grayscaleMatrix = null;
@@ -83,9 +89,14 @@
final ContentResolver cr = context.getContentResolver();
final DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
- final boolean invertColors = Secure.getIntForUser(cr,
- Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, userId) != 0;
- dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_INVERT_COLOR,
- invertColors ? MATRIX_INVERT_COLOR : null);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ final boolean invertColors = Secure.getIntForUser(cr,
+ Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, userId) != 0;
+ dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_INVERT_COLOR,
+ invertColors ? MATRIX_INVERT_COLOR : null);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index a789157..d1a1bd4 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -302,7 +302,7 @@
*/
private final boolean isBluetoothPersistedStateOn() {
return Settings.Global.getInt(mContentResolver,
- Settings.Global.BLUETOOTH_ON, 0) != BLUETOOTH_OFF;
+ Settings.Global.BLUETOOTH_ON, BLUETOOTH_ON_BLUETOOTH) != BLUETOOTH_OFF;
}
/**
@@ -310,7 +310,7 @@
*/
private final boolean isBluetoothPersistedStateOnBluetooth() {
return Settings.Global.getInt(mContentResolver,
- Settings.Global.BLUETOOTH_ON, 0) == BLUETOOTH_ON_BLUETOOTH;
+ Settings.Global.BLUETOOTH_ON, BLUETOOTH_ON_BLUETOOTH) == BLUETOOTH_ON_BLUETOOTH;
}
/**
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b08549f..667a4a9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -24,6 +24,7 @@
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -262,6 +263,11 @@
DONT_REAP
};
+ private enum UnneededFor {
+ LINGER, // Determine whether this network is unneeded and should be lingered.
+ TEARDOWN, // Determine whether this network is unneeded and should be torn down.
+ }
+
/**
* used internally to change our mobile data enabled flag
*/
@@ -392,6 +398,11 @@
private static final int EVENT_REQUEST_LINKPROPERTIES = 32;
private static final int EVENT_REQUEST_NETCAPABILITIES = 33;
+ /**
+ * Used internally to (re)configure avoid bad wifi setting.
+ */
+ private static final int EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI = 34;
+
/** Handler thread used for both of the handlers below. */
@VisibleForTesting
protected final HandlerThread mHandlerThread;
@@ -691,13 +702,13 @@
if (DBG) log("ConnectivityService starting up");
mMetricsLog = logger;
- mDefaultRequest = createInternetRequestForTransport(-1);
+ mDefaultRequest = createInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder());
mNetworkRequests.put(mDefaultRequest, defaultNRI);
mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI);
mDefaultMobileDataRequest = createInternetRequestForTransport(
- NetworkCapabilities.TRANSPORT_CELLULAR);
+ NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST);
mHandlerThread = createHandlerThread();
mHandlerThread.start();
@@ -846,17 +857,26 @@
Settings.Global.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
LingerMonitor.DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS);
mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit);
+
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ mHandler.sendEmptyMessage(EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI);
+ }
+ }, UserHandle.ALL, intentFilter, null, null);
+ updateAvoidBadWifi();
}
- private NetworkRequest createInternetRequestForTransport(int transportType) {
+ private NetworkRequest createInternetRequestForTransport(
+ int transportType, NetworkRequest.Type type) {
NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
if (transportType > -1) {
netCap.addTransportType(transportType);
}
- return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(),
- NetworkRequest.Type.REQUEST);
+ return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type);
}
// Used only for testing.
@@ -896,6 +916,12 @@
mSettingsObserver.observe(
Settings.Global.getUriFor(Settings.Global.MOBILE_DATA_ALWAYS_ON),
EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON);
+
+ // Watch for whether to automatically switch away from wifi networks that lose Internet
+ // access.
+ mSettingsObserver.observe(
+ Settings.Global.getUriFor(Settings.Global.NETWORK_AVOID_BAD_WIFI),
+ EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI);
}
private synchronized int nextNetworkRequestId() {
@@ -1964,8 +1990,12 @@
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
pw.println(nai.toString());
pw.increaseIndent();
- pw.println(String.format("Requests: %d request/%d total",
- nai.numRequestNetworkRequests(), nai.numNetworkRequests()));
+ pw.println(String.format(
+ "Requests: REQUEST:%d LISTEN:%d BACKGROUND_REQUEST:%d total:%d",
+ nai.numForegroundNetworkRequests(),
+ nai.numNetworkRequests() - nai.numRequestNetworkRequests(),
+ nai.numBackgroundNetworkRequests(),
+ nai.numNetworkRequests()));
pw.increaseIndent();
for (int i = 0; i < nai.numNetworkRequests(); i++) {
pw.println(nai.requestAt(i).toString());
@@ -2124,15 +2154,11 @@
case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
final NetworkCapabilities networkCapabilities = (NetworkCapabilities) msg.obj;
if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) ||
- networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
+ networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) ||
+ networkCapabilities.hasCapability(NET_CAPABILITY_FOREGROUND)) {
Slog.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
}
- if (nai.everConnected && !nai.networkCapabilities.equalImmutableCapabilities(
- networkCapabilities)) {
- Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities: "
- + nai.networkCapabilities + " -> " + networkCapabilities);
- }
- updateCapabilities(nai, networkCapabilities);
+ updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
break;
}
case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: {
@@ -2203,13 +2229,14 @@
if (nai != null) {
final boolean valid =
(msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
+ final boolean wasValidated = nai.lastValidated;
if (DBG) log(nai.name() + " validation " + (valid ? "passed" : "failed") +
(msg.obj == null ? "" : " with redirect to " + (String)msg.obj));
if (valid != nai.lastValidated) {
final int oldScore = nai.getCurrentScore();
nai.lastValidated = valid;
nai.everValidated |= valid;
- updateCapabilities(nai, nai.networkCapabilities);
+ updateCapabilities(oldScore, nai, nai.networkCapabilities);
// If score has changed, rebroadcast to NetworkFactories. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
}
@@ -2221,6 +2248,9 @@
NetworkAgent.CMD_REPORT_NETWORK_STATUS,
(valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
0, redirectUrlBundle);
+ if (wasValidated && !nai.lastValidated) {
+ handleNetworkUnvalidated(nai);
+ }
}
break;
}
@@ -2233,9 +2263,10 @@
}
// If captive portal status has changed, update capabilities.
if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
+ final int oldScore = nai.getCurrentScore();
nai.lastCaptivePortalDetected = visible;
nai.everCaptivePortalDetected |= visible;
- updateCapabilities(nai, nai.networkCapabilities);
+ updateCapabilities(oldScore, nai, nai.networkCapabilities);
}
if (!visible) {
mNotifier.clearNotification(netId);
@@ -2286,15 +2317,13 @@
// 3. If this network is unneeded (which implies it is not lingering), and there is at least
// one lingered request, start lingering.
nai.updateLingerTimer();
- if (nai.isLingering() && nai.numRequestNetworkRequests() > 0) {
+ if (nai.isLingering() && nai.numForegroundNetworkRequests() > 0) {
if (DBG) log("Unlingering " + nai.name());
nai.unlinger();
logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER);
- } else if (unneeded(nai) && nai.getLingerExpiry() > 0) { // unneeded() calls isLingering()
+ } else if (unneeded(nai, UnneededFor.LINGER) && nai.getLingerExpiry() > 0) {
int lingerTime = (int) (nai.getLingerExpiry() - now);
- if (DBG) {
- Log.d(TAG, "Lingering " + nai.name() + " for " + lingerTime + "ms");
- }
+ if (DBG) log("Lingering " + nai.name() + " for " + lingerTime + "ms");
nai.linger();
logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER);
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
@@ -2473,15 +2502,37 @@
}
}
- // Is nai unneeded by all NetworkRequests (and should be disconnected)?
- // This is whether it is satisfying any NetworkRequests or were it to become validated,
- // would it have a chance of satisfying any NetworkRequests.
- private boolean unneeded(NetworkAgentInfo nai) {
- if (!nai.everConnected || nai.isVPN() ||
- nai.isLingering() || nai.numRequestNetworkRequests() > 0) {
+ // Determines whether the network is the best (or could become the best, if it validated), for
+ // none of a particular type of NetworkRequests. The type of NetworkRequests considered depends
+ // on the value of reason:
+ //
+ // - UnneededFor.TEARDOWN: non-listen NetworkRequests. If a network is unneeded for this reason,
+ // then it should be torn down.
+ // - UnneededFor.LINGER: foreground NetworkRequests. If a network is unneeded for this reason,
+ // then it should be lingered.
+ private boolean unneeded(NetworkAgentInfo nai, UnneededFor reason) {
+ final int numRequests;
+ switch (reason) {
+ case TEARDOWN:
+ numRequests = nai.numRequestNetworkRequests();
+ break;
+ case LINGER:
+ numRequests = nai.numForegroundNetworkRequests();
+ break;
+ default:
+ Slog.wtf(TAG, "Invalid reason. Cannot happen.");
+ return true;
+ }
+
+ if (!nai.everConnected || nai.isVPN() || nai.isLingering() || numRequests > 0) {
return false;
}
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (reason == UnneededFor.LINGER && nri.request.isBackgroundRequest()) {
+ // Background requests don't affect lingering.
+ continue;
+ }
+
// If this Network is already the highest scoring Network for a request, or if
// there is hope for it to become one if it validated, then it is needed.
if (nri.request.isRequest() && nai.satisfies(nri.request) &&
@@ -2583,6 +2634,7 @@
boolean wasKept = false;
NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
if (nai != null) {
+ boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
nai.removeRequest(nri.request.requestId);
if (VDBG) {
log(" Removing from current network " + nai.name() +
@@ -2591,13 +2643,17 @@
// If there are still lingered requests on this network, don't tear it down,
// but resume lingering instead.
updateLingerState(nai, SystemClock.elapsedRealtime());
- if (unneeded(nai)) {
+ if (unneeded(nai, UnneededFor.TEARDOWN)) {
if (DBG) log("no live requests for " + nai.name() + "; disconnecting");
teardownUnneededNetwork(nai);
} else {
wasKept = true;
}
mNetworkForRequestId.remove(nri.request.requestId);
+ if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) {
+ // Went from foreground to background.
+ updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
+ }
}
// TODO: remove this code once we know that the Slog.wtf is never hit.
@@ -2711,6 +2767,56 @@
PROMPT_UNVALIDATED_DELAY_MS);
}
+ private boolean mAvoidBadWifi;
+
+ public boolean avoidBadWifi() {
+ return mAvoidBadWifi;
+ }
+
+ @VisibleForTesting
+ public boolean updateAvoidBadWifi() {
+ // There are two modes: either we always automatically avoid unvalidated wifi, or we show a
+ // dialog and don't switch to it. The behaviour is controlled by the NETWORK_AVOID_BAD_WIFI
+ // setting. If the setting has no value, then the value is taken from the config value,
+ // which can be changed via OEM/carrier overlays.
+ //
+ // The only valid values for NETWORK_AVOID_BAD_WIFI are null and unset. Currently, the unit
+ // test uses 0 in order to avoid having to mock out fetching the carrier setting.
+ int defaultAvoidBadWifi =
+ mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi);
+ int avoid = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.NETWORK_AVOID_BAD_WIFI, defaultAvoidBadWifi);
+
+ boolean prev = mAvoidBadWifi;
+ mAvoidBadWifi = (avoid == 1);
+ return mAvoidBadWifi != prev;
+ }
+
+ private void showValidationNotification(NetworkAgentInfo nai, NotificationType type) {
+ final String action;
+ switch (type) {
+ case NO_INTERNET:
+ action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED;
+ break;
+ case LOST_INTERNET:
+ action = ConnectivityManager.ACTION_PROMPT_LOST_VALIDATION;
+ break;
+ default:
+ Slog.wtf(TAG, "Unknown notification type " + type);
+ return;
+ }
+
+ Intent intent = new Intent(action);
+ intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClassName("com.android.settings",
+ "com.android.settings.wifi.WifiNoInternetDialog");
+
+ PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
+ mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ mNotifier.showNotification(nai.network.netId, type, nai, null, pendingIntent, true);
+ }
+
private void handlePromptUnvalidated(Network network) {
if (VDBG) log("handlePromptUnvalidated " + network);
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
@@ -2722,18 +2828,22 @@
!nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
return;
}
+ showValidationNotification(nai, NotificationType.NO_INTERNET);
+ }
- Intent intent = new Intent(ConnectivityManager.ACTION_PROMPT_UNVALIDATED);
- intent.setData(Uri.fromParts("netId", Integer.toString(network.netId), null));
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClassName("com.android.settings",
- "com.android.settings.wifi.WifiNoInternetDialog");
+ // TODO: Delete this like updateMobileDataAlwaysOn above.
+ @VisibleForTesting
+ void updateNetworkAvoidBadWifi() {
+ mHandler.sendEmptyMessage(EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI);
+ }
- PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
- mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
+ NetworkCapabilities nc = nai.networkCapabilities;
+ if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc);
- mNotifier.showNotification(nai.network.netId, NotificationType.NO_INTERNET, nai, null,
- pendingIntent, true);
+ if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && !avoidBadWifi()) {
+ showValidationNotification(nai, NotificationType.LOST_INTERNET);
+ }
}
private class InternalHandler extends Handler {
@@ -2822,6 +2932,12 @@
handleMobileDataAlwaysOn();
break;
}
+ case EVENT_CONFIGURE_NETWORK_AVOID_BAD_WIFI: {
+ if (updateAvoidBadWifi()) {
+ rematchAllNetworksAndRequests(null, 0);
+ }
+ break;
+ }
case EVENT_REQUEST_LINKPROPERTIES:
handleRequestLinkProperties((NetworkRequest) msg.obj, msg.arg1);
break;
@@ -4472,10 +4588,19 @@
* augmented with any stateful capabilities implied from {@code networkAgent}
* (e.g., validated status and captive portal status).
*
+ * @param oldScore score of the network before any of the changes that prompted us
+ * to call this function.
* @param nai the network having its capabilities updated.
* @param networkCapabilities the new network capabilities.
*/
- private void updateCapabilities(NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) {
+ private void updateCapabilities(
+ int oldScore, NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) {
+ if (nai.everConnected && !nai.networkCapabilities.equalImmutableCapabilities(
+ networkCapabilities)) {
+ Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities: "
+ + nai.networkCapabilities + " -> " + networkCapabilities);
+ }
+
// Don't modify caller's NetworkCapabilities.
networkCapabilities = new NetworkCapabilities(networkCapabilities);
if (nai.lastValidated) {
@@ -4488,21 +4613,39 @@
} else {
networkCapabilities.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
}
- if (!Objects.equals(nai.networkCapabilities, networkCapabilities)) {
- final int oldScore = nai.getCurrentScore();
- if (nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) !=
- networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
- try {
- mNetd.setNetworkPermission(nai.network.netId,
- networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) ?
- null : NetworkManagementService.PERMISSION_SYSTEM);
- } catch (RemoteException e) {
- loge("Exception in setNetworkPermission: " + e);
- }
+ if (nai.isBackgroundNetwork()) {
+ networkCapabilities.removeCapability(NET_CAPABILITY_FOREGROUND);
+ } else {
+ networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
+ }
+
+ if (Objects.equals(nai.networkCapabilities, networkCapabilities)) return;
+
+ if (nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) !=
+ networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
+ try {
+ mNetd.setNetworkPermission(nai.network.netId,
+ networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) ?
+ null : NetworkManagementService.PERMISSION_SYSTEM);
+ } catch (RemoteException e) {
+ loge("Exception in setNetworkPermission: " + e);
}
- synchronized (nai) {
- nai.networkCapabilities = networkCapabilities;
- }
+ }
+
+ final NetworkCapabilities prevNc = nai.networkCapabilities;
+ synchronized (nai) {
+ nai.networkCapabilities = networkCapabilities;
+ }
+ if (nai.getCurrentScore() == oldScore &&
+ networkCapabilities.equalRequestableCapabilities(prevNc)) {
+ // If the requestable capabilities haven't changed, and the score hasn't changed, then
+ // the change we're processing can't affect any requests, it can only affect the listens
+ // on this network. We might have been called by rematchNetworkAndRequests when a
+ // network changed foreground state.
+ processListenRequests(nai, true);
+ } else {
+ // If the requestable capabilities have changed or the score changed, we can't have been
+ // called by rematchNetworkAndRequests, so it's safe to start a rematch.
rematchAllNetworksAndRequests(nai, oldScore);
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
@@ -4625,8 +4768,13 @@
// must be no other active linger timers, and we must stop lingering.
oldNetwork.clearLingerState();
- if (unneeded(oldNetwork)) {
+ if (unneeded(oldNetwork, UnneededFor.TEARDOWN)) {
+ // Tear the network down.
teardownUnneededNetwork(oldNetwork);
+ } else {
+ // Put the network in the background.
+ updateCapabilities(oldNetwork.getCurrentScore(), oldNetwork,
+ oldNetwork.networkCapabilities);
}
}
@@ -4644,6 +4792,31 @@
setDefaultDnsSystemProperties(newNetwork.linkProperties.getDnsServers());
}
+ private void processListenRequests(NetworkAgentInfo nai, boolean capabilitiesChanged) {
+ // For consistency with previous behaviour, send onLost callbacks before onAvailable.
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ NetworkRequest nr = nri.request;
+ if (!nr.isListen()) continue;
+ if (nai.isSatisfyingRequest(nr.requestId) && !nai.satisfies(nr)) {
+ nai.removeRequest(nri.request.requestId);
+ callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0);
+ }
+ }
+
+ if (capabilitiesChanged) {
+ notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+ }
+
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ NetworkRequest nr = nri.request;
+ if (!nr.isListen()) continue;
+ if (nai.satisfies(nr) && !nai.isSatisfyingRequest(nr.requestId)) {
+ nai.addRequest(nr);
+ notifyNetworkCallback(nai, nri);
+ }
+ }
+ }
+
// Handles a network appearing or improving its score.
//
// - Evaluates all current NetworkRequests that can be
@@ -4677,13 +4850,25 @@
boolean keep = newNetwork.isVPN();
boolean isNewDefault = false;
NetworkAgentInfo oldDefaultNetwork = null;
+
+ final boolean wasBackgroundNetwork = newNetwork.isBackgroundNetwork();
+ final int score = newNetwork.getCurrentScore();
+
if (VDBG) log("rematching " + newNetwork.name());
+
// Find and migrate to this Network any NetworkRequests for
// which this network is now the best.
ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<NetworkAgentInfo>();
ArrayList<NetworkRequestInfo> addedRequests = new ArrayList<NetworkRequestInfo>();
- if (VDBG) log(" network has: " + newNetwork.networkCapabilities);
+ NetworkCapabilities nc = newNetwork.networkCapabilities;
+ if (VDBG) log(" network has: " + nc);
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ // Process requests in the first pass and listens in the second pass. This allows us to
+ // change a network's capabilities depending on which requests it has. This is only
+ // correct if the change in capabilities doesn't affect whether the network satisfies
+ // requests or not, and doesn't affect the network's score.
+ if (nri.request.isListen()) continue;
+
final NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
final boolean satisfies = newNetwork.satisfies(nri.request);
if (newNetwork == currentNetwork && satisfies) {
@@ -4698,22 +4883,14 @@
// check if it satisfies the NetworkCapabilities
if (VDBG) log(" checking if request is satisfied: " + nri.request);
if (satisfies) {
- if (nri.request.isListen()) {
- // This is not a request, it's a callback listener.
- // Add it to newNetwork regardless of score.
- if (newNetwork.addRequest(nri.request)) addedRequests.add(nri);
- continue;
- }
-
// next check if it's better than any current network we're using for
// this request
if (VDBG) {
log("currentScore = " +
(currentNetwork != null ? currentNetwork.getCurrentScore() : 0) +
- ", newScore = " + newNetwork.getCurrentScore());
+ ", newScore = " + score);
}
- if (currentNetwork == null ||
- currentNetwork.getCurrentScore() < newNetwork.getCurrentScore()) {
+ if (currentNetwork == null || currentNetwork.getCurrentScore() < score) {
if (VDBG) log("rematch for " + newNetwork.name());
if (currentNetwork != null) {
if (VDBG) log(" accepting network in place of " + currentNetwork.name());
@@ -4735,7 +4912,7 @@
// TODO - this could get expensive if we have alot of requests for this
// network. Think about if there is a way to reduce this. Push
// netid->request mapping to each factory?
- sendUpdatedScoreToFactories(nri.request, newNetwork.getCurrentScore());
+ sendUpdatedScoreToFactories(nri.request, score);
if (isDefaultRequest(nri)) {
isNewDefault = true;
oldDefaultNetwork = currentNetwork;
@@ -4747,7 +4924,7 @@
} else if (newNetwork.isSatisfyingRequest(nri.request.requestId)) {
// If "newNetwork" is listed as satisfying "nri" but no longer satisfies "nri",
// mark it as no longer satisfying "nri". Because networks are processed by
- // rematchAllNetworkAndRequests() in descending score order, "currentNetwork" will
+ // rematchAllNetworksAndRequests() in descending score order, "currentNetwork" will
// match "newNetwork" before this loop will encounter a "currentNetwork" with higher
// score than "newNetwork" and where "currentNetwork" no longer satisfies "nri".
// This means this code doesn't have to handle the case where "currentNetwork" no
@@ -4761,16 +4938,14 @@
mNetworkForRequestId.remove(nri.request.requestId);
sendUpdatedScoreToFactories(nri.request, 0);
} else {
- if (nri.request.isRequest()) {
- Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
- newNetwork.name() +
- " without updating mNetworkForRequestId or factories!");
- }
+ Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
+ newNetwork.name() +
+ " without updating mNetworkForRequestId or factories!");
}
- // TODO: technically, sending CALLBACK_LOST here is
- // incorrect if nri is a request (not a listen) and there
- // is a replacement network currently connected that can
- // satisfy it. However, the only capability that can both
+ // TODO: Technically, sending CALLBACK_LOST here is
+ // incorrect if there is a replacement network currently
+ // connected that can satisfy nri, which is a request
+ // (not a listen). However, the only capability that can both
// a) be requested and b) change is NET_CAPABILITY_TRUSTED,
// so this code is only incorrect for a network that loses
// the TRUSTED capability, which is a rare case.
@@ -4795,6 +4970,28 @@
}
}
+ if (!newNetwork.networkCapabilities.equalRequestableCapabilities(nc)) {
+ Slog.wtf(TAG, String.format(
+ "BUG: %s changed requestable capabilities during rematch: %s -> %s",
+ nc, newNetwork.networkCapabilities));
+ }
+ if (newNetwork.getCurrentScore() != score) {
+ Slog.wtf(TAG, String.format(
+ "BUG: %s changed score during rematch: %d -> %d",
+ score, newNetwork.getCurrentScore()));
+ }
+
+ // Second pass: process all listens.
+ if (wasBackgroundNetwork != newNetwork.isBackgroundNetwork()) {
+ // If the network went from background to foreground or vice versa, we need to update
+ // its foreground state. It is safe to do this after rematching the requests because
+ // NET_CAPABILITY_FOREGROUND does not affect requests, as is not a requestable
+ // capability and does not affect the network's score (see the Slog.wtf call above).
+ updateCapabilities(score, newNetwork, newNetwork.networkCapabilities);
+ } else {
+ processListenRequests(newNetwork, false);
+ }
+
// do this after the default net is switched, but
// before LegacyTypeTracker sends legacy broadcasts
for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri);
@@ -4872,7 +5069,7 @@
}
if (reapUnvalidatedNetworks == ReapUnvalidatedNetworks.REAP) {
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
- if (unneeded(nai)) {
+ if (unneeded(nai, UnneededFor.TEARDOWN)) {
if (nai.getLingerExpiry() > 0) {
// This network has active linger timers and no requests, but is not
// lingering. Linger it.
@@ -4986,6 +5183,10 @@
if (!networkAgent.created
&& (state == NetworkInfo.State.CONNECTED
|| (state == NetworkInfo.State.CONNECTING && networkAgent.isVPN()))) {
+
+ // A network that has just connected has zero requests and is thus a foreground network.
+ networkAgent.networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
+
try {
// This should never fail. Specifying an already in use NetID will cause failure.
if (networkAgent.isVPN()) {
@@ -5147,6 +5348,8 @@
NetworkRequest nr = networkAgent.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
if (VDBG) log(" sending notification for " + nr);
+ // TODO: if we're in the middle of a rematch, can we send a CAP_CHANGED callback for
+ // a network that no longer satisfies the listen?
if (nri.mPendingIntent == null) {
callCallbackForRequest(nri, networkAgent, notifyType, arg1);
} else {
@@ -5281,6 +5484,9 @@
}
}
}
+
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 71ac544..df1b6f5 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -822,7 +822,7 @@
public void onUnlockUser(final @UserIdInt int userHandle) {
// Called on ActivityManager thread.
mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER,
- userHandle));
+ userHandle /* arg1 */, 0 /* arg2 */));
}
}
@@ -3077,8 +3077,8 @@
if (DEBUG) {
Slog.d(TAG, "Found an input method " + p);
}
- } catch (XmlPullParserException | IOException e) {
- Slog.w(TAG, "Unable to load input method " + compName, e);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Unable to load input method " + compName, e);
}
}
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 3fdcceb..83d374c 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -364,6 +364,7 @@
buildResolveList(intent, categories, debug, defaultOnly,
resolvedType, scheme, listCut.get(i), resultList, userId);
}
+ filterResults(resultList);
sortResults(resultList);
return resultList;
}
@@ -457,6 +458,7 @@
buildResolveList(intent, categories, debug, defaultOnly,
resolvedType, scheme, schemeCut, finalList, userId);
}
+ filterResults(finalList);
sortResults(finalList);
if (debug) {
@@ -521,6 +523,12 @@
Collections.sort(results, mResolvePrioritySorter);
}
+ /**
+ * Apply filtering to the results. This happens before the results are sorted.
+ */
+ protected void filterResults(List<R> results) {
+ }
+
protected void dumpFilter(PrintWriter out, String prefix, F filter) {
out.print(prefix); out.println(filter);
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 48e9ac7..b152372 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -928,14 +928,16 @@
String interestedPackages = null;
try {
String[] allPackages = mPackageManager.getPackagesForUid(uid);
- for(String aPackage : allPackages) {
- ApplicationInfo ai = mPackageManager.getApplicationInfo(aPackage,
- PackageManager.GET_META_DATA);
- Bundle b = ai.metaData;
- if(b == null) {
- return;
+ if (allPackages != null) {
+ for(String aPackage : allPackages) {
+ ApplicationInfo ai = mPackageManager.getApplicationInfo(aPackage,
+ PackageManager.GET_META_DATA);
+ Bundle b = ai.metaData;
+ if(b == null) {
+ return;
+ }
+ interestedPackages = b.getString("android.accounts.SupportedLoginTypes");
}
- interestedPackages = b.getString("android.accounts.SupportedLoginTypes");
}
} catch (PackageManager.NameNotFoundException e) {
Log.d("NameNotFoundException", e.getMessage());
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 96706c0..f740b4b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -286,7 +286,6 @@
import static android.provider.Settings.Global.LENIENT_BACKGROUND_CHECK;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
import static android.provider.Settings.System.FONT_SCALE;
-import static android.security.KeyChain.ACTION_TRUST_STORE_CHANGED;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
@@ -555,7 +554,7 @@
public IntentFirewall mIntentFirewall;
// Whether we should show our dialogs (ANR, crash, etc) or just perform their
- // default actuion automatically. Important for devices without direct input
+ // default action automatically. Important for devices without direct input
// devices.
private boolean mShowDialogs = true;
private boolean mInVrMode = false;
@@ -627,7 +626,8 @@
final AppErrors mAppErrors;
public boolean canShowErrorDialogs() {
- return mShowDialogs && !mSleeping && !mShuttingDown;
+ return mShowDialogs && !mSleeping && !mShuttingDown
+ && mLockScreenShown != LOCK_SCREEN_SHOWN;
}
private static final class PriorityState {
@@ -1120,19 +1120,18 @@
final AppOpsService mAppOpsService;
/**
- * Current configuration information. HistoryRecord objects are given
- * a reference to this object to indicate which configuration they are
- * currently running in, so this object must be kept immutable.
+ * Current global configuration information. Contains general settings for the entire system,
+ * also corresponds to the merged configuration of the default display.
*/
- Configuration mConfiguration = new Configuration();
+ Configuration mGlobalConfiguration = new Configuration();
/**
* Current sequencing integer of the configuration, for skipping old
* configurations.
*/
- int mConfigurationSeq = 0;
+ private int mConfigurationSeq;
- boolean mSuppressResizeConfigChanges = false;
+ boolean mSuppressResizeConfigChanges;
/**
* Hardware-reported OpenGLES version.
@@ -2303,7 +2302,7 @@
callingPackage = r.info.getComponentName();
if (mInVrMode != vrMode) {
mInVrMode = vrMode;
- mShowDialogs = shouldShowDialogs(mConfiguration, mInVrMode);
+ mShowDialogs = shouldShowDialogs(mGlobalConfiguration, mInVrMode);
if (r.app != null) {
ProcessRecord proc = r.app;
if (proc.vrThreadTid > 0) {
@@ -2679,10 +2678,10 @@
mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
- mConfiguration.setToDefaults();
- mConfiguration.setLocales(LocaleList.getDefault());
+ mGlobalConfiguration.setToDefaults();
+ mGlobalConfiguration.setLocales(LocaleList.getDefault());
- mConfigurationSeq = mConfiguration.seq = 1;
+ mConfigurationSeq = mGlobalConfiguration.seq = 1;
mProcessCpuTracker.init();
mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
@@ -3119,8 +3118,8 @@
}
final void showUnsupportedZoomDialogIfNeededLocked(ActivityRecord r) {
- if (mConfiguration.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
- && r.appInfo.requiresSmallestWidthDp > mConfiguration.smallestScreenWidthDp) {
+ if (mGlobalConfiguration.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
+ && r.appInfo.requiresSmallestWidthDp > mGlobalConfiguration.smallestScreenWidthDp) {
final Message msg = Message.obtain();
msg.what = SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG;
msg.obj = r;
@@ -4724,7 +4723,7 @@
final long origId = Binder.clearCallingIdentity();
mWindowManager.setAppOrientation(r.appToken, requestedOrientation);
Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mConfiguration, r.mayFreezeScreenLocked(r.app) ? r.appToken : null);
+ mGlobalConfiguration, r.mayFreezeScreenLocked(r.app) ? r.appToken : null);
if (config != null) {
r.frozenBeforeDestroy = true;
if (!updateConfigurationLocked(config, r, false)) {
@@ -6507,7 +6506,7 @@
PackageManager.NOTIFY_PACKAGE_USE_INSTRUMENTATION);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Binding proc "
- + processName + " with config " + mConfiguration);
+ + processName + " with config " + mGlobalConfiguration);
ApplicationInfo appInfo = app.instrumentationInfo != null
? app.instrumentationInfo : app.info;
app.compat = compatibilityInfoForPackageLocked(appInfo);
@@ -6521,7 +6520,7 @@
app.instrumentationUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
- new Configuration(mConfiguration), app.compat,
+ new Configuration(mGlobalConfiguration), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked());
updateLruProcessLocked(app, false, null);
@@ -9281,7 +9280,7 @@
r.task.stack.getDisplaySize(displaySize);
thumbnailInfo.taskWidth = displaySize.x;
thumbnailInfo.taskHeight = displaySize.y;
- thumbnailInfo.screenOrientation = mConfiguration.orientation;
+ thumbnailInfo.screenOrientation = mGlobalConfiguration.orientation;
TaskRecord task = new TaskRecord(this,
mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
@@ -13113,11 +13112,11 @@
mSupportsFreeformWindowManagement = false;
mSupportsPictureInPicture = false;
}
- // This happens before any activities are started, so we can
- // change mConfiguration in-place.
+ // This happens before any activities are started, so we can change global configuration
+ // in-place.
updateConfigurationLocked(configuration, null, true);
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Initial config: " + mConfiguration);
+ "Initial config: " + mGlobalConfiguration);
// Load resources only after the current configuration has been set.
final Resources res = mContext.getResources();
@@ -13132,10 +13131,11 @@
com.android.internal.R.string.config_appsNotReportingCrashes));
mUserController.mUserSwitchUiEnabled = !res.getBoolean(
com.android.internal.R.bool.config_customUserSwitchUi);
- if ((mConfiguration.uiMode & UI_MODE_TYPE_TELEVISION) == UI_MODE_TYPE_TELEVISION) {
+ if ((mGlobalConfiguration.uiMode & UI_MODE_TYPE_TELEVISION)
+ == UI_MODE_TYPE_TELEVISION) {
mFullscreenThumbnailScale = (float) res
.getInteger(com.android.internal.R.integer.thumbnail_width_tv) /
- (float) mConfiguration.screenWidthDp;
+ (float) mGlobalConfiguration.screenWidthDp;
} else {
mFullscreenThumbnailScale = res.getFraction(
com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
@@ -14728,7 +14728,7 @@
pw.println(" mHeavyWeightProcess: " + mHeavyWeightProcess);
}
if (dumpPackage == null) {
- pw.println(" mConfiguration: " + mConfiguration);
+ pw.println(" mGlobalConfiguration: " + mGlobalConfiguration);
}
if (dumpAll) {
pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange);
@@ -18711,15 +18711,15 @@
public ConfigurationInfo getDeviceConfigurationInfo() {
ConfigurationInfo config = new ConfigurationInfo();
synchronized (this) {
- config.reqTouchScreen = mConfiguration.touchscreen;
- config.reqKeyboardType = mConfiguration.keyboard;
- config.reqNavigation = mConfiguration.navigation;
- if (mConfiguration.navigation == Configuration.NAVIGATION_DPAD
- || mConfiguration.navigation == Configuration.NAVIGATION_TRACKBALL) {
+ config.reqTouchScreen = mGlobalConfiguration.touchscreen;
+ config.reqKeyboardType = mGlobalConfiguration.keyboard;
+ config.reqNavigation = mGlobalConfiguration.navigation;
+ if (mGlobalConfiguration.navigation == Configuration.NAVIGATION_DPAD
+ || mGlobalConfiguration.navigation == Configuration.NAVIGATION_TRACKBALL) {
config.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
}
- if (mConfiguration.keyboard != Configuration.KEYBOARD_UNDEFINED
- && mConfiguration.keyboard != Configuration.KEYBOARD_NOKEYS) {
+ if (mGlobalConfiguration.keyboard != Configuration.KEYBOARD_UNDEFINED
+ && mGlobalConfiguration.keyboard != Configuration.KEYBOARD_NOKEYS) {
config.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD;
}
config.reqGlEsVersion = GL_ES_VERSION;
@@ -18743,7 +18743,7 @@
public Configuration getConfiguration() {
Configuration ci;
synchronized(this) {
- ci = new Configuration(mConfiguration);
+ ci = new Configuration(mGlobalConfiguration);
ci.userSetLocale = false;
}
return ci;
@@ -18776,8 +18776,8 @@
@Override
public void updatePersistentConfiguration(Configuration values) {
enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
- "updateConfiguration()");
- enforceWriteSettingsPermission("updateConfiguration()");
+ "updatePersistentConfiguration()");
+ enforceWriteSettingsPermission("updatePersistentConfiguration()");
if (values == null) {
throw new NullPointerException("Configuration must not be null");
}
@@ -18801,7 +18801,7 @@
private void updateFontScaleIfNeeded(@UserIdInt int userId) {
final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(),
FONT_SCALE, 1.0f, userId);
- if (mConfiguration.fontScale != scaleFactor) {
+ if (mGlobalConfiguration.fontScale != scaleFactor) {
final Configuration configuration = mWindowManager.computeNewConfiguration();
configuration.fontScale = scaleFactor;
synchronized (this) {
@@ -18853,7 +18853,7 @@
}
void updateUserConfigurationLocked() {
- Configuration configuration = new Configuration(mConfiguration);
+ Configuration configuration = new Configuration(mGlobalConfiguration);
Settings.System.adjustConfigurationForUser(mContext.getContentResolver(), configuration,
mUserController.getCurrentUserIdLocked(), Settings.System.canWrite(mContext));
updateConfigurationLocked(configuration, null, false);
@@ -18892,7 +18892,7 @@
mWindowManager.deferSurfaceLayout();
}
if (values != null) {
- Configuration newConfig = new Configuration(mConfiguration);
+ Configuration newConfig = new Configuration(mGlobalConfiguration);
changes = newConfig.updateFrom(values);
if (changes != 0) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
@@ -18923,13 +18923,13 @@
mConfigurationSeq = 1;
}
newConfig.seq = mConfigurationSeq;
- mConfiguration = newConfig;
+ mGlobalConfiguration = newConfig;
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
mUsageStatsService.reportConfigurationChange(newConfig,
mUserController.getCurrentUserIdLocked());
//mUsageStatsService.noteStartConfig(newConfig);
- final Configuration configCopy = new Configuration(mConfiguration);
+ final Configuration configCopy = new Configuration(mGlobalConfiguration);
// TODO: If our config changes, should we auto dismiss any currently
// showing dialogs?
@@ -18970,7 +18970,7 @@
try {
if (app.thread != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
- + app.processName + " new config " + mConfiguration);
+ + app.processName + " new config " + mGlobalConfiguration);
app.thread.scheduleConfigurationChanged(configCopy);
}
} catch (Exception e) {
@@ -18999,7 +18999,8 @@
// relaunches if necessary. This way we don't need to relaunch again below in
// ensureActivityConfigurationLocked().
if (mWindowManager != null) {
- final int[] resizedStacks = mWindowManager.setNewConfiguration(mConfiguration);
+ final int[] resizedStacks =
+ mWindowManager.setNewConfiguration(mGlobalConfiguration);
if (resizedStacks != null) {
for (int stackId : resizedStacks) {
final Rect newBounds = mWindowManager.getBoundsForNewConfiguration(stackId);
@@ -19044,7 +19045,7 @@
* A thought: SystemUI might also want to get told about this, the Power
* dialog / global actions also might want different behaviors.
*/
- private static final boolean shouldShowDialogs(Configuration config, boolean inVrMode) {
+ private static boolean shouldShowDialogs(Configuration config, boolean inVrMode) {
final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS
&& config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH
&& config.navigation == Configuration.NAVIGATION_NONAV);
@@ -22159,4 +22160,22 @@
Binder.restoreCallingIdentity(callingId);
}
}
+
+ @Override
+ public boolean canBypassWorkChallenge(PendingIntent intent) throws RemoteException {
+ final int userId = intent.getCreatorUserHandle().getIdentifier();
+ if (!mUserController.isUserRunningLocked(userId, ActivityManager.FLAG_AND_LOCKED)) {
+ return false;
+ }
+ IIntentSender target = intent.getTarget();
+ if (!(target instanceof PendingIntentRecord)) {
+ return false;
+ }
+ final PendingIntentRecord record = (PendingIntentRecord) target;
+ final ResolveInfo rInfo = mStackSupervisor.resolveIntent(record.key.requestIntent,
+ record.key.requestResolvedType, userId, PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ // For direct boot aware activities, they can be shown without triggering a work challenge
+ // before the profile user is unlocked.
+ return rInfo != null && rInfo.activityInfo != null;
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 4af6435..caaa77a 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManager.StackId;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER;
@@ -945,21 +946,29 @@
// The activity now gets access to the data associated with this Intent.
service.grantUriPermissionFromIntentLocked(callingUid, packageName,
intent, getUriPermissionsLocked(), userId);
- // We want to immediately deliver the intent to the activity if
- // it is currently the top resumed activity... however, if the
- // device is sleeping, then all activities are stopped, so in that
- // case we will deliver it if this is the current top activity on its
- // stack.
final ReferrerIntent rintent = new ReferrerIntent(intent, referrer);
boolean unsent = true;
- if ((state == ActivityState.RESUMED
- || (service.isSleepingLocked() && task.stack != null
- && task.stack.topRunningActivityLocked() == this))
- && app != null && app.thread != null) {
+ final ActivityStack stack = task.stack;
+ final boolean isTopActivityInStack =
+ stack != null && stack.topRunningActivityLocked() == this;
+ final boolean isTopActivityWhileSleeping =
+ service.isSleepingLocked() && isTopActivityInStack;
+ final boolean isTopActivityInMinimizedDockedStack = isTopActivityInStack
+ && stack.mStackId == DOCKED_STACK_ID && mStackSupervisor.mIsDockMinimized
+ && state == ActivityState.PAUSED;
+
+ // We want to immediately deliver the intent to the activity if:
+ // - It is the resumed activity.
+ // - The device is sleeping and it is the top activity behind the lock screen (b/6700897).
+ // - It is the top activity in a minimized docked stack. In this case the activity will be
+ // temporarily resumed then paused again on the client side.
+ if ((state == ActivityState.RESUMED || isTopActivityWhileSleeping
+ || isTopActivityInMinimizedDockedStack) && app != null && app.thread != null) {
try {
ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
ar.add(rintent);
- app.thread.scheduleNewIntent(ar, appToken);
+ app.thread.scheduleNewIntent(
+ ar, appToken, isTopActivityInMinimizedDockedStack /* andPause */);
unsent = false;
} catch (RemoteException e) {
Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 055cd57..06eeb2c 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1303,7 +1303,9 @@
// It is possible the activity was freezing the screen before it was paused.
// In that case go ahead and remove the freeze this activity has on the screen
// since it is no longer visible.
- prev.stopFreezingScreenLocked(true /*force*/);
+ if (prev != null) {
+ prev.stopFreezingScreenLocked(true /*force*/);
+ }
mPausingActivity = null;
}
@@ -2495,7 +2497,7 @@
boolean notUpdated = true;
if (mStackSupervisor.isFocusedStack(this)) {
Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mService.mConfiguration,
+ mService.mGlobalConfiguration,
next.mayFreezeScreenLocked(next.app) ? next.appToken : null);
if (config != null) {
next.frozenBeforeDestroy = true;
@@ -2550,7 +2552,8 @@
break;
}
}
- next.app.thread.scheduleNewIntent(next.newIntents, next.appToken);
+ next.app.thread.scheduleNewIntent(
+ next.newIntents, next.appToken, false /* andPause */);
}
// Well the app will no longer be stopped.
@@ -4540,7 +4543,7 @@
// Short circuit: if the two configurations are equal (the common case), then there is
// nothing to do.
- final Configuration newConfig = mService.mConfiguration;
+ final Configuration newConfig = mService.mGlobalConfiguration;
r.task.sanitizeOverrideConfiguration(newConfig);
final Configuration taskConfig = r.task.mOverrideConfig;
if (r.configuration.equals(newConfig)
@@ -4770,7 +4773,7 @@
r.forceNewConfig = false;
mStackSupervisor.activityRelaunchingLocked(r);
r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes,
- !andResume, new Configuration(mService.mConfiguration),
+ !andResume, new Configuration(mService.mGlobalConfiguration),
new Configuration(r.task.mOverrideConfig), preserveWindow);
// Note: don't need to call pauseIfSleepingLocked() here, because
// the caller will only pass in 'andResume' if this activity is
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 647c2ec..80d51e5 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1191,7 +1191,7 @@
// just restarting it anyway.
if (checkConfig) {
Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mService.mConfiguration,
+ mService.mGlobalConfiguration,
r.mayFreezeScreenLocked(app) ? r.appToken : null);
// Deferring resume here because we're going to launch new activity shortly.
// We don't want to perform a redundant launch of the same record while ensuring
@@ -1282,7 +1282,8 @@
}
app.forceProcessStateUpTo(mService.mTopProcessState);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
- System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
+ System.identityHashCode(r), r.info,
+ new Configuration(mService.mGlobalConfiguration),
new Configuration(task.mOverrideConfig), r.compat, r.launchedFromPackage,
task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
@@ -2272,12 +2273,12 @@
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + task.taskId);
- final Configuration overrideConfig = task.updateOverrideConfiguration(bounds);
+ final boolean updatedConfig = task.updateOverrideConfiguration(bounds);
// This variable holds information whether the configuration didn't change in a significant
// way and the activity was kept the way it was. If it's false, it means the activity had
// to be relaunched due to configuration change.
boolean kept = true;
- if (overrideConfig != null) {
+ if (updatedConfig) {
final ActivityRecord r = task.topRunningActivityLocked();
if (r != null) {
final ActivityStack stack = task.stack;
diff --git a/services/core/java/com/android/server/am/ActivityStartInterceptor.java b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
index a8ea5f4..547161a 100644
--- a/services/core/java/com/android/server/am/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
@@ -29,6 +29,7 @@
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -209,6 +210,11 @@
if (!mService.mUserController.shouldConfirmCredentials(userId)) {
return null;
}
+ // Allow direct boot aware activity to be displayed before the user is unlocked.
+ if (aInfo.directBootAware && mService.mUserController.isUserRunningLocked(userId,
+ ActivityManager.FLAG_AND_LOCKED)) {
+ return null;
+ }
final IIntentSender target = mService.getIntentSenderLocked(
INTENT_SENDER_ACTIVITY, callingPackage,
Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent },
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index bd0d9b8..975b23e1 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -99,11 +99,9 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManagerInternal;
-import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -471,7 +469,7 @@
}
ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
- intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
+ intent, resolvedType, aInfo, mService.mGlobalConfiguration, resultRecord, resultWho,
requestCode, componentSpecified, voiceSession != null, mSupervisor, container,
options, sourceRecord);
if (outActivity != null) {
@@ -713,8 +711,8 @@
String callingPackage, Intent intent, String resolvedType,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
- ProfilerInfo profilerInfo, IActivityManager.WaitResult outResult, Configuration config,
- Bundle bOptions, boolean ignoreTargetSecurity, int userId,
+ ProfilerInfo profilerInfo, IActivityManager.WaitResult outResult,
+ Configuration globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, int userId,
IActivityContainer iContainer, TaskRecord inTask) {
// Refuse possible leaked file descriptors
if (intent != null && intent.hasFileDescriptors()) {
@@ -783,7 +781,8 @@
} else {
stack = container.mStack;
}
- stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0;
+ stack.mConfigWillChange = globalConfig != null
+ && mService.mGlobalConfiguration.diff(globalConfig) != 0;
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Starting activity when config will change = " + stack.mConfigWillChange);
@@ -872,7 +871,7 @@
stack.mConfigWillChange = false;
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Updating to new configuration after starting activity.");
- mService.updateConfigurationLocked(config, null, false);
+ mService.updateConfigurationLocked(globalConfig, null, false);
}
if (outResult != null) {
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 576f2b2..c19a571 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -22,7 +22,6 @@
import com.android.internal.os.ProcessCpuTracker;
import com.android.server.Watchdog;
-import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityThread;
@@ -33,10 +32,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDataObserver;
-import android.content.pm.PackageManager;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -59,7 +55,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
-import java.util.concurrent.Semaphore;
import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
@@ -359,7 +354,7 @@
return;
}
- Message msg = Message.obtain();
+ final Message msg = Message.obtain();
msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;
task = data.task;
@@ -773,6 +768,12 @@
} else if (app.crashing) {
Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
return;
+ } else if (app.killedByAm) {
+ Slog.i(TAG, "App already killed by AM skipping ANR: " + app + " " + annotation);
+ return;
+ } else if (app.killed) {
+ Slog.i(TAG, "Skipping died app ANR: " + app + " " + annotation);
+ return;
}
// In case we come through here for the same app before completing
diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java
index a54df4b..0b282ed 100644
--- a/services/core/java/com/android/server/am/CompatModePackages.java
+++ b/services/core/java/com/android/server/am/CompatModePackages.java
@@ -197,8 +197,8 @@
}
public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
- CompatibilityInfo ci = new CompatibilityInfo(ai, mService.mConfiguration.screenLayout,
- mService.mConfiguration.smallestScreenWidthDp,
+ CompatibilityInfo ci = new CompatibilityInfo(ai, mService.mGlobalConfiguration.screenLayout,
+ mService.mGlobalConfiguration.smallestScreenWidthDp,
(getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0);
//Slog.i(TAG, "*********** COMPAT FOR PKG " + ai.packageName + ": " + ci);
return ci;
@@ -207,8 +207,8 @@
public int computeCompatModeLocked(ApplicationInfo ai) {
boolean enabled = (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0;
CompatibilityInfo info = new CompatibilityInfo(ai,
- mService.mConfiguration.screenLayout,
- mService.mConfiguration.smallestScreenWidthDp, enabled);
+ mService.mGlobalConfiguration.screenLayout,
+ mService.mGlobalConfiguration.smallestScreenWidthDp, enabled);
if (info.alwaysSupportsScreen()) {
return ActivityManager.COMPAT_MODE_NEVER;
}
@@ -408,8 +408,8 @@
out.startTag(null, "compat-packages");
final IPackageManager pm = AppGlobals.getPackageManager();
- final int screenLayout = mService.mConfiguration.screenLayout;
- final int smallestScreenWidthDp = mService.mConfiguration.smallestScreenWidthDp;
+ final int screenLayout = mService.mGlobalConfiguration.screenLayout;
+ final int smallestScreenWidthDp = mService.mGlobalConfiguration.smallestScreenWidthDp;
final Iterator<Map.Entry<String, Integer>> it = pkgs.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 6cdabaa..43eb251 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -17,7 +17,6 @@
package com.android.server.am;
import android.annotation.NonNull;
-import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Debug;
@@ -25,7 +24,6 @@
import android.os.FileUtils;
import android.os.Process;
import android.os.SystemClock;
-import android.provider.Settings;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
@@ -82,7 +80,7 @@
private static final String PERSISTED_TASK_IDS_FILENAME = "persisted_taskIds.txt";
static final String IMAGE_EXTENSION = ".png";
- @VisibleForTesting static final String TAG_TASK = "task";
+ private static final String TAG_TASK = "task";
private final ActivityManagerService mService;
private final ActivityStackSupervisor mStackSupervisor;
@@ -409,43 +407,18 @@
return null;
}
- @VisibleForTesting
List<TaskRecord> restoreTasksForUserLocked(final int userId) {
final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
File userTasksDir = getUserTasksDir(userId);
+
File[] recentFiles = userTasksDir.listFiles();
if (recentFiles == null) {
Slog.e(TAG, "restoreTasksForUserLocked: Unable to list files from " + userTasksDir);
return tasks;
}
- // Get the last persist uptime so we know how to adjust the first/last active times for each
- // task
- ContentResolver cr = mService.mContext.getContentResolver();
- long lastPersistUptime = Settings.Secure.getLong(cr,
- Settings.Secure.TASK_PERSISTER_LAST_WRITE_UPTIME, 0);
- if (DEBUG) {
- Slog.d(TaskPersister.TAG, "restoreTasksForUserLocked: lastPersistUptime=" +
- lastPersistUptime);
- }
-
- // Adjust the overview last visible task active time as we adjust the task active times when
- // loading. See TaskRecord.restoreFromXml(). If we have not migrated yet, SystemUI will
- // migrate the old value into the system setting.
- if (lastPersistUptime > 0) {
- long overviewLastActiveTime = Settings.Secure.getLongForUser(cr,
- Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME, 0, userId);
- if (DEBUG) {
- Slog.d(TaskPersister.TAG, "restoreTasksForUserLocked: overviewLastActiveTime=" +
- overviewLastActiveTime + " lastPersistUptime=" + lastPersistUptime);
- }
- Settings.Secure.putLongForUser(cr,
- Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME,
- -lastPersistUptime + overviewLastActiveTime, userId);
- }
-
for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
File taskFile = recentFiles[taskNdx];
if (DEBUG) {
@@ -466,11 +439,15 @@
if (event == XmlPullParser.START_TAG) {
if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: START_TAG name=" + name);
if (TAG_TASK.equals(name)) {
- final TaskRecord task = TaskRecord.restoreFromXml(in, mService,
- mStackSupervisor, lastPersistUptime);
+ final TaskRecord task = TaskRecord.restoreFromXml(in, mStackSupervisor);
if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: restored task="
+ task);
if (task != null) {
+ // XXX Don't add to write queue... there is no reason to write
+ // out the stuff we just read, if we don't write it we will
+ // read the same thing again.
+ // mWriteQueue.add(new TaskWriteQueueItem(task));
+
final int taskId = task.taskId;
if (mStackSupervisor.anyTaskForIdLocked(taskId,
/* restoreFromRecents= */ false, 0) != null) {
@@ -486,12 +463,6 @@
task.isPersistable = true;
tasks.add(task);
recoveredTaskIds.add(taskId);
-
- // We've shifted the first and last active times, so we need to
- // persist the new task data to disk otherwise they will not
- // have the updated values. This is only done once whenever
- // the recents are first loaded for the user.
- wakeup(task, false);
}
} else {
Slog.e(TAG, "restoreTasksForUserLocked: Unable to restore taskFile="
@@ -780,15 +751,6 @@
}
}
}
-
- // Always update the task persister uptime when updating any tasks
- if (DEBUG) {
- Slog.d(TAG, "LazyTaskWriter: Updating last write uptime=" +
- SystemClock.elapsedRealtime());
- }
- Settings.Secure.putLong(mService.mContext.getContentResolver(),
- Settings.Secure.TASK_PERSISTER_LAST_WRITE_UPTIME,
- SystemClock.elapsedRealtime());
}
}
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 206dd45..013d61b 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -39,14 +39,12 @@
import android.os.Debug;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.util.DisplayMetrics;
import android.util.Slog;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.util.XmlUtils;
@@ -153,10 +151,8 @@
ComponentName realActivity; // The actual activity component that started the task.
boolean realActivitySuspended; // True if the actual activity component that started the
// task is suspended.
- long firstActiveTime; // First time this task was active, relative to boot time. This can be
- // negative if this task was last used prior to boot.
- long lastActiveTime; // Last time this task was active, relative to boot time. This can be
- // negative if this task was last used prior to boot.
+ long firstActiveTime; // First time this task was active.
+ long lastActiveTime; // Last time this task was active, including sleep.
boolean inRecents; // Actually in the recents list?
boolean isAvailable; // Is the activity available to be launched?
boolean rootWasReset; // True if the intent at the root of the task had
@@ -275,6 +271,7 @@
// This number will be assigned when we evaluate OOM scores for all visible tasks.
int mLayerRank = -1;
+ /** Contains configurations settings that are different from the parent's configuration. */
Configuration mOverrideConfig = Configuration.EMPTY;
TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
@@ -384,14 +381,14 @@
}
void touchActiveTime() {
- lastActiveTime = SystemClock.elapsedRealtime();
+ lastActiveTime = System.currentTimeMillis();
if (firstActiveTime == 0) {
firstActiveTime = lastActiveTime;
}
}
long getInactiveDuration() {
- return SystemClock.elapsedRealtime() - lastActiveTime;
+ return System.currentTimeMillis() - lastActiveTime;
}
/** Sets the original intent, and the calling uid and package. */
@@ -462,9 +459,8 @@
rootWasReset = true;
}
userId = UserHandle.getUserId(info.applicationInfo.uid);
- mUserSetupComplete = mService != null &&
- Settings.Secure.getIntForUser(mService.mContext.getContentResolver(),
- USER_SETUP_COMPLETE, 0, userId) != 0;
+ mUserSetupComplete = Settings.Secure.getIntForUser(mService.mContext.getContentResolver(),
+ USER_SETUP_COMPLETE, 0, userId) != 0;
if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
// If the activity itself has requested auto-remove, then just always do it.
autoRemoveRecents = true;
@@ -580,7 +576,7 @@
* @return whether the thumbnail was set
*/
boolean setLastThumbnailLocked(Bitmap thumbnail) {
- final Configuration serviceConfig = mService.mConfiguration;
+ final Configuration serviceConfig = mService.mGlobalConfiguration;
int taskWidth = 0;
int taskHeight = 0;
if (mBounds != null) {
@@ -1189,9 +1185,7 @@
if (lastTaskDescription != null) {
lastTaskDescription.saveToXml(out);
}
- if (mLastThumbnailInfo != null) {
- mLastThumbnailInfo.saveToXml(out);
- }
+ mLastThumbnailInfo.saveToXml(out);
out.attribute(null, ATTR_TASK_AFFILIATION_COLOR, String.valueOf(mAffiliatedTaskColor));
out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId));
out.attribute(null, ATTR_PREV_AFFILIATION, String.valueOf(mPrevAffiliateTaskId));
@@ -1213,11 +1207,9 @@
out.endTag(null, TAG_AFFINITYINTENT);
}
- if (intent != null) {
- out.startTag(null, TAG_INTENT);
- intent.saveToXml(out);
- out.endTag(null, TAG_INTENT);
- }
+ out.startTag(null, TAG_INTENT);
+ intent.saveToXml(out);
+ out.endTag(null, TAG_INTENT);
final ArrayList<ActivityRecord> activities = mActivities;
final int numActivities = activities.size();
@@ -1236,9 +1228,8 @@
}
}
- static TaskRecord restoreFromXml(XmlPullParser in, ActivityManagerService service,
- ActivityStackSupervisor stackSupervisor, long lastPersistUptime)
- throws IOException, XmlPullParserException {
+ static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor)
+ throws IOException, XmlPullParserException {
Intent intent = null;
Intent affinityIntent = null;
ArrayList<ActivityRecord> activities = new ArrayList<>();
@@ -1351,31 +1342,6 @@
}
}
- if (lastPersistUptime > 0) {
- if (TaskPersister.DEBUG) {
- Slog.d(TaskPersister.TAG, "TaskRecord: Adjust firstActiveTime=" + firstActiveTime +
- " lastPersistUptime=" + lastPersistUptime);
- Slog.d(TaskPersister.TAG, "TaskRecord: Migrate lastActiveTime=" + lastActiveTime +
- " lastActiveTime=" + lastPersistUptime);
- }
- // The first and last task active times are relative to the last boot time, so offset
- // them to be prior to the current boot time
- firstActiveTime = -lastPersistUptime + firstActiveTime;
- lastActiveTime = -lastPersistUptime + lastActiveTime;
- } else {
- // The first/last active times are still absolute clock times, so offset them to be
- // relative to the current boot time
- long currentTime = System.currentTimeMillis();
- if (TaskPersister.DEBUG) {
- Slog.d(TaskPersister.TAG, "TaskRecord: Migrate firstActiveTime=" + firstActiveTime +
- " currentTime=" + currentTime);
- Slog.d(TaskPersister.TAG, "TaskRecord: Migrate lastActiveTime=" + lastActiveTime +
- " currentTime=" + currentTime);
- }
- firstActiveTime = -Math.max(0, currentTime - firstActiveTime);
- lastActiveTime = -Math.max(0, currentTime - lastActiveTime);
- }
-
int event;
while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
(event != XmlPullParser.END_TAG || in.getDepth() >= outerDepth)) {
@@ -1425,7 +1391,7 @@
+ ": effectiveUid=" + effectiveUid);
}
- final TaskRecord task = new TaskRecord(service, taskId, intent,
+ final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootHasReset,
autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription,
activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
@@ -1488,9 +1454,9 @@
/**
* Update task's override configuration based on the bounds.
* @param bounds The bounds of the task.
- * @return Update configuration or null if there is no change.
+ * @return True if the override configuration was updated.
*/
- Configuration updateOverrideConfiguration(Rect bounds) {
+ boolean updateOverrideConfiguration(Rect bounds) {
return updateOverrideConfiguration(bounds, null /* insetBounds */);
}
@@ -1500,11 +1466,11 @@
* @param insetBounds The bounds used to calculate the system insets, which is used here to
* subtract the navigation bar/status bar size from the screen size reported
* to the application. See {@link IActivityManager#resizeDockedStack}.
- * @return Update configuration or null if there is no change.
+ * @return True if the override configuration was updated.
*/
- Configuration updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) {
+ boolean updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) {
if (Objects.equals(mBounds, bounds)) {
- return null;
+ return false;
}
final Configuration oldConfig = mOverrideConfig;
final boolean oldFullscreen = mFullscreen;
@@ -1535,7 +1501,7 @@
mService.mStackSupervisor.scheduleReportMultiWindowModeChanged(this);
}
- return !mOverrideConfig.equals(oldConfig) ? mOverrideConfig : null;
+ return !mOverrideConfig.equals(oldConfig);
}
private void subtractNonDecorInsets(Rect inOutBounds, Rect inInsetBounds,
@@ -1573,7 +1539,7 @@
// For calculating screenWidthDp, screenWidthDp, we use the stable inset screen area,
// i.e. the screen area without the system bars.
- final Configuration serviceConfig = mService.mConfiguration;
+ final Configuration serviceConfig = mService.mGlobalConfiguration;
final Configuration config = new Configuration(Configuration.EMPTY);
// TODO(multidisplay): Update Dp to that of display stack is on.
final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
@@ -1820,7 +1786,7 @@
pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
pw.print(" isResizeable=" + isResizeable());
- pw.print(" firstActiveTime=" + firstActiveTime);
+ pw.print(" firstActiveTime=" + lastActiveTime);
pw.print(" lastActiveTime=" + lastActiveTime);
pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0d90832..96ba259 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -513,8 +513,11 @@
private int mDeviceOrientation = Configuration.ORIENTATION_UNDEFINED;
// Request to override default use of A2DP for media.
- // FIXME: remove when MediaRouter does not use setBluetoothA2dpOn() anymore
private boolean mBluetoothA2dpEnabled;
+ // FIXME: remove when MediaRouter does not use setBluetoothA2dpOn() anymore
+ // state of bluetooth A2DP enable request sen by deprecated APIs setBluetoothA2dpOn() and
+ // isBluettohA2dpOn()
+ private boolean mBluetoothA2dpEnabledExternal;
private final Object mBluetoothA2dpEnabledLock = new Object();
// Monitoring of audio routes. Protected by mCurAudioRoutes.
@@ -848,6 +851,12 @@
RotationHelper.updateOrientation();
}
+ synchronized (mBluetoothA2dpEnabledLock) {
+ AudioSystem.setForceUse(AudioSystem.FOR_MEDIA,
+ mBluetoothA2dpEnabled ?
+ AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP);
+ }
+
synchronized (mSettingsLock) {
AudioSystem.setForceUse(AudioSystem.FOR_DOCK,
mDockAudioMediaEnabled ?
@@ -2715,7 +2724,7 @@
* @deprecated
* */
public void setBluetoothA2dpOn(boolean on) {
- mBluetoothA2dpEnabled = on;
+ mBluetoothA2dpEnabledExternal = on;
Log.e(TAG, "setBluetoothA2dpOn() is deprecated, now a no-op",
new Exception("Deprecated use of setBluetoothA2dpOn()"));
}
@@ -2725,7 +2734,7 @@
* @deprecated
* */
public boolean isBluetoothA2dpOn() {
- return mBluetoothA2dpEnabled;
+ return mBluetoothA2dpEnabledExternal;
}
/** @see AudioManager#startBluetoothSco() */
@@ -3392,6 +3401,10 @@
private int checkForRingerModeChange(int oldIndex, int direction, int step, boolean isMuted,
String caller, int flags) {
int result = FLAG_ADJUST_VOLUME;
+ if (isPlatformTelevision()) {
+ return result;
+ }
+
int ringerMode = getRingerModeInternal();
switch (ringerMode) {
@@ -3794,6 +3807,11 @@
Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
+ address + ")");
}
+ if ((state == 0) && ((type == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
+ (type == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) ||
+ (type == AudioSystem.DEVICE_OUT_LINE))) {
+ setBluetoothA2dpOnInt(true);
+ }
int delay = checkSendBecomingNoisyIntent(type, state);
queueMsgUnderWakeLock(mAudioHandler,
MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
@@ -4774,6 +4792,7 @@
VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, streamState, 0);
+ setBluetoothA2dpOnInt(true);
AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
AudioSystem.DEVICE_STATE_AVAILABLE, address, name);
// Reset A2DP suspend state each time a new sink is connected
@@ -5011,6 +5030,7 @@
devices |= dev;
}
}
+
if (devices == device) {
sendMsg(mAudioHandler,
MSG_BROADCAST_AUDIO_BECOMING_NOISY,
@@ -5114,6 +5134,11 @@
return;
}
if (state != 0) {
+ if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
+ (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) ||
+ (device == AudioSystem.DEVICE_OUT_LINE)) {
+ setBluetoothA2dpOnInt(false);
+ }
if ((device & mSafeMediaVolumeDevices) != 0) {
sendMsg(mAudioHandler,
MSG_CHECK_MUSIC_ACTIVE,
@@ -5579,9 +5604,26 @@
}
}
+ // Handles request to override default use of A2DP for media.
+ // Must be called synchronized on mConnectedDevices
+ public void setBluetoothA2dpOnInt(boolean on) {
+ synchronized (mBluetoothA2dpEnabledLock) {
+ mBluetoothA2dpEnabled = on;
+ setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA,
+ mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP);
+ }
+ }
+
// Must be called synchronized on mConnectedDevices
private void setForceUseInt_SyncDevices(int usage, int config) {
switch (usage) {
+ case AudioSystem.FOR_MEDIA:
+ if (config == AudioSystem.FORCE_NO_BT_A2DP) {
+ mBecomingNoisyIntentDevices &= ~AudioSystem.DEVICE_OUT_ALL_A2DP;
+ } else { // config == AudioSystem.FORCE_NONE
+ mBecomingNoisyIntentDevices |= AudioSystem.DEVICE_OUT_ALL_A2DP;
+ }
+ break;
case AudioSystem.FOR_DOCK:
if (config == AudioSystem.FORCE_ANALOG_DOCK) {
mBecomingNoisyIntentDevices |= AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET;
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index 49be879..cc181141 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -40,9 +40,9 @@
private static final String TAG = "MediaFocusControl";
private static final boolean DEBUG = false;
- private AudioFocusDeathHandler mDeathHandler;
- private final IAudioFocusDispatcher mFocusDispatcher; // may be null
- private final IBinder mSourceRef;
+ private AudioFocusDeathHandler mDeathHandler; // may be null
+ private IAudioFocusDispatcher mFocusDispatcher; // may be null
+ private final IBinder mSourceRef; // may be null
private final String mClientId;
private final String mPackageName;
private final int mCallingUid;
@@ -205,6 +205,7 @@
if (mSourceRef != null && mDeathHandler != null) {
mSourceRef.unlinkToDeath(mDeathHandler, 0);
mDeathHandler = null;
+ mFocusDispatcher = null;
}
} catch (java.util.NoSuchElementException e) {
Log.e(TAG, "FocusRequester.release() hit ", e);
@@ -275,12 +276,13 @@
mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
- if (mFocusDispatcher != null) {
+ final IAudioFocusDispatcher fd = mFocusDispatcher;
+ if (fd != null) {
if (DEBUG) {
Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
+ mClientId);
}
- mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
+ fd.dispatchAudioFocusChange(focusGain, mClientId);
}
} catch (android.os.RemoteException e) {
Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
@@ -311,14 +313,15 @@
toAudioFocusInfo(), false /* wasDispatched */);
return;
}
- if (mFocusDispatcher != null) {
+ final IAudioFocusDispatcher fd = mFocusDispatcher;
+ if (fd != null) {
if (DEBUG) {
Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
+ mClientId);
}
mFocusController.notifyExtPolicyFocusLoss_syncAf(
toAudioFocusInfo(), true /* wasDispatched */);
- mFocusDispatcher.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
+ fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
}
}
} catch (android.os.RemoteException e) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 278d70b..206834e 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -160,6 +160,7 @@
Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
+ clientToRemove);
stackIterator.remove();
+ // stack entry not used anymore, clear references
fr.release();
}
}
@@ -171,7 +172,7 @@
* Called synchronized on mAudioFocusLock
* Remove focus listeners from the focus stack for a particular client when it has died.
*/
- private void removeFocusStackEntryForClient(IBinder cb) {
+ private void removeFocusStackEntryOnDeath(IBinder cb) {
// is the owner of the audio focus part of the client to remove?
boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() &&
mFocusStack.peek().hasSameBinder(cb);
@@ -181,9 +182,10 @@
while(stackIterator.hasNext()) {
FocusRequester fr = stackIterator.next();
if(fr.hasSameBinder(cb)) {
- Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb);
+ Log.i(TAG, "AudioFocus removeFocusStackEntryOnDeath(): removing entry for " + cb);
stackIterator.remove();
- // the client just died, no need to unlink to its death
+ // stack entry not used anymore, clear references
+ fr.release();
}
}
if (isTopOfStackForClientToRemove) {
@@ -257,14 +259,9 @@
public void binderDied() {
synchronized(mAudioFocusLock) {
- Log.w(TAG, " AudioFocus audio focus client died");
- removeFocusStackEntryForClient(mCb);
+ removeFocusStackEntryOnDeath(mCb);
}
}
-
- public IBinder getBinder() {
- return mCb;
- }
}
/**
@@ -420,6 +417,7 @@
// (premature death == death before abandoning focus)
// Register for client death notification
AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
+
try {
cb.linkToDeath(afdh, 0);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index b0330b9..cb4bb88 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -104,15 +104,18 @@
// -----------------------------------------------
// If a network has no chance of satisfying any requests (even if it were to become validated
// and enter state #5), ConnectivityService will disconnect the NetworkAgent's AsyncChannel.
-// If the network ever for any period of time had satisfied a NetworkRequest (i.e. had been
-// the highest scoring that satisfied the NetworkRequest's constraints), but is no longer the
-// highest scoring network for any NetworkRequest, then there will be a 30s pause before
-// ConnectivityService disconnects the NetworkAgent's AsyncChannel. During this pause the
-// network is considered "lingering". This pause exists to allow network communication to be
-// wrapped up rather than abruptly terminated. During this pause if the network begins satisfying
-// a NetworkRequest, ConnectivityService will cancel the future disconnection of the NetworkAgent's
-// AsyncChannel, and the network is no longer considered "lingering".
+//
+// If the network was satisfying a foreground NetworkRequest (i.e. had been the highest scoring that
+// satisfied the NetworkRequest's constraints), but is no longer the highest scoring network for any
+// foreground NetworkRequest, then there will be a 30s pause to allow network communication to be
+// wrapped up rather than abruptly terminated. During this pause the network is said to be
+// "lingering". During this pause if the network begins satisfying a foreground NetworkRequest,
+// ConnectivityService will cancel the future disconnection of the NetworkAgent's AsyncChannel, and
+// the network is no longer considered "lingering". After the linger timer expires, if the network
+// is satisfying one or more background NetworkRequests it is kept up in the background. If it is
+// not, ConnectivityService disconnects the NetworkAgent's AsyncChannel.
public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
+
public NetworkInfo networkInfo;
// This Network object should always be used if possible, so as to encourage reuse of the
// enclosed socket factory and connection pool. Avoid creating other Network objects.
@@ -227,11 +230,13 @@
// The list of NetworkRequests being satisfied by this Network.
private final SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>();
- // The list of NetworkRequests that this Network previously satisfied with the highest
- // score. A non-empty list indicates that if this Network was validated it is lingered.
+
// How many of the satisfied requests are actual requests and not listens.
private int mNumRequestNetworkRequests = 0;
+ // How many of the satisfied requests are of type BACKGROUND_REQUEST.
+ private int mNumBackgroundNetworkRequests = 0;
+
public final Messenger messenger;
public final AsyncChannel asyncChannel;
@@ -265,6 +270,32 @@
//
// These functions must only called on ConnectivityService's main thread.
+ private static final boolean ADD = true;
+ private static final boolean REMOVE = false;
+
+ private void updateRequestCounts(boolean add, NetworkRequest request) {
+ int delta = add ? +1 : -1;
+ switch (request.type) {
+ case REQUEST:
+ case TRACK_DEFAULT:
+ mNumRequestNetworkRequests += delta;
+ break;
+
+ case BACKGROUND_REQUEST:
+ mNumRequestNetworkRequests += delta;
+ mNumBackgroundNetworkRequests += delta;
+ break;
+
+ case LISTEN:
+ break;
+
+ case NONE:
+ default:
+ Log.wtf(TAG, "Unhandled request type " + request.type);
+ break;
+ }
+ }
+
/**
* Add {@code networkRequest} to this network as it's satisfied by this network.
* @return true if {@code networkRequest} was added or false if {@code networkRequest} was
@@ -273,9 +304,15 @@
public boolean addRequest(NetworkRequest networkRequest) {
NetworkRequest existing = mNetworkRequests.get(networkRequest.requestId);
if (existing == networkRequest) return false;
- if (existing != null && existing.isRequest()) mNumRequestNetworkRequests--;
+ if (existing != null) {
+ // Should only happen if the requestId wraps. If that happens lots of other things will
+ // be broken as well.
+ Log.wtf(TAG, String.format("Duplicate requestId for %s and %s on %s",
+ networkRequest, existing, name()));
+ updateRequestCounts(REMOVE, existing);
+ }
mNetworkRequests.put(networkRequest.requestId, networkRequest);
- if (networkRequest.isRequest()) mNumRequestNetworkRequests++;
+ updateRequestCounts(ADD, networkRequest);
return true;
}
@@ -285,9 +322,9 @@
public void removeRequest(int requestId) {
NetworkRequest existing = mNetworkRequests.get(requestId);
if (existing == null) return;
+ updateRequestCounts(REMOVE, existing);
mNetworkRequests.remove(requestId);
if (existing.isRequest()) {
- mNumRequestNetworkRequests--;
unlingerRequest(existing);
}
}
@@ -316,12 +353,37 @@
}
/**
+ * Returns the number of requests currently satisfied by this network of type
+ * {@link android.net.NetworkRequest.Type.BACKGROUND_REQUEST}.
+ */
+ public int numBackgroundNetworkRequests() {
+ return mNumBackgroundNetworkRequests;
+ }
+
+ /**
+ * Returns the number of foreground requests currently satisfied by this network.
+ */
+ public int numForegroundNetworkRequests() {
+ return mNumRequestNetworkRequests - mNumBackgroundNetworkRequests;
+ }
+
+ /**
* Returns the number of requests of any type currently satisfied by this network.
*/
public int numNetworkRequests() {
return mNetworkRequests.size();
}
+ /**
+ * Returns whether the network is a background network. A network is a background network if it
+ * is satisfying no foreground requests and at least one background request. (If it did not have
+ * a background request, it would be a speculative network that is only being kept up because
+ * it might satisfy a request if it validated).
+ */
+ public boolean isBackgroundNetwork() {
+ return !isVPN() && numForegroundNetworkRequests() == 0 && mNumBackgroundNetworkRequests > 0;
+ }
+
// Does this network satisfy request?
public boolean satisfies(NetworkRequest request) {
return created &&
@@ -354,16 +416,20 @@
}
int score = currentScore;
- // Use NET_CAPABILITY_VALIDATED here instead of lastValidated, this allows
- // ConnectivityService.updateCapabilities() to compute the old score prior to updating
- // networkCapabilities (with a potentially different validated state).
- if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) && !pretendValidated) {
+ if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty()) {
score -= UNVALIDATED_SCORE_PENALTY;
}
if (score < 0) score = 0;
return score;
}
+ // Return true on devices configured to ignore score penalty for wifi networks
+ // that become unvalidated (b/31075769).
+ private boolean ignoreWifiUnvalidationPenalty() {
+ boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
+ return isWifi && !mConnService.avoidBadWifi() && everValidated;
+ }
+
// Get the current score for this Network. This may be modified from what the
// NetworkAgent sent, as it has modifiers applied to it.
public int getCurrentScore() {
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 99926a9..f7b01be 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -35,7 +35,7 @@
public class NetworkNotificationManager {
- public static enum NotificationType { SIGN_IN, NO_INTERNET, NETWORK_SWITCH };
+ public static enum NotificationType { SIGN_IN, NO_INTERNET, LOST_INTERNET, NETWORK_SWITCH };
private static final String NOTIFICATION_ID = "Connectivity.Notification";
@@ -91,8 +91,8 @@
* @param id an identifier that uniquely identifies this notification. This must match
* between show and hide calls. We use the NetID value but for legacy callers
* we concatenate the range of types with the range of NetIDs.
- * @param nai the network with which the notification is associated. For a SIGN_IN or
- * NO_INTERNET notification, this is the network we're connecting to. For a
+ * @param nai the network with which the notification is associated. For a SIGN_IN, NO_INTERNET,
+ * or LOST_INTERNET notification, this is the network we're connecting to. For a
* NETWORK_SWITCH notification it's the network that we switched from. When this network
* disconnects the notification is removed.
* @param switchToNai for a NETWORK_SWITCH notification, the network we are switching to. Null
@@ -126,6 +126,10 @@
if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
title = r.getString(R.string.wifi_no_internet, 0);
details = r.getString(R.string.wifi_no_internet_detailed);
+ } else if (notifyType == NotificationType.LOST_INTERNET &&
+ transportType == TRANSPORT_WIFI) {
+ title = r.getString(R.string.wifi_no_internet, 0);
+ details = r.getString(R.string.wifi_no_internet_detailed);
} else if (notifyType == NotificationType.SIGN_IN) {
switch (transportType) {
case TRANSPORT_WIFI:
diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java
index 7d1da01..58c76ec 100644
--- a/services/core/java/com/android/server/connectivity/PacManager.java
+++ b/services/core/java/com/android/server/connectivity/PacManager.java
@@ -27,6 +27,7 @@
import android.net.ProxyInfo;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -39,10 +40,10 @@
import com.android.net.IProxyCallback;
import com.android.net.IProxyPortListener;
import com.android.net.IProxyService;
-import com.android.server.IoThread;
import libcore.io.Streams;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
@@ -66,6 +67,7 @@
private static final int DELAY_1 = 0;
private static final int DELAY_4 = 3;
private static final int DELAY_LONG = 4;
+ private static final long MAX_PAC_SIZE = 20 * 1000 * 1000;
/** Keep these values up-to-date with ProxyService.java */
public static final String KEY_PROXY = "keyProxy";
@@ -123,15 +125,21 @@
}
};
+ private final HandlerThread mNetThread = new HandlerThread("android.pacmanager",
+ android.os.Process.THREAD_PRIORITY_DEFAULT);
+ private final Handler mNetThreadHandler;
+
class PacRefreshIntentReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
- IoThread.getHandler().post(mPacDownloader);
+ mNetThreadHandler.post(mPacDownloader);
}
}
public PacManager(Context context, Handler handler, int proxyMessage) {
mContext = context;
mLastPort = -1;
+ mNetThread.start();
+ mNetThreadHandler = new Handler(mNetThread.getLooper());
mPacRefreshIntent = PendingIntent.getBroadcast(
context, 0, new Intent(ACTION_PAC_REFRESH), 0);
@@ -199,7 +207,25 @@
private static String get(Uri pacUri) throws IOException {
URL url = new URL(pacUri.toString());
URLConnection urlConnection = url.openConnection(java.net.Proxy.NO_PROXY);
- return new String(Streams.readFully(urlConnection.getInputStream()));
+ long contentLength = -1;
+ try {
+ contentLength = Long.parseLong(urlConnection.getHeaderField("Content-Length"));
+ } catch (NumberFormatException e) {
+ // Ignore
+ }
+ if (contentLength > MAX_PAC_SIZE) {
+ throw new IOException("PAC too big: " + contentLength + " bytes");
+ }
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int count;
+ while ((count = urlConnection.getInputStream().read(buffer)) != -1) {
+ bytes.write(buffer, 0, count);
+ if (bytes.size() > MAX_PAC_SIZE) {
+ throw new IOException("PAC too big");
+ }
+ }
+ return bytes.toString();
}
private int getNextDelay(int currentDelay) {
@@ -267,7 +293,7 @@
intent.setClassName(PAC_PACKAGE, PAC_SERVICE);
if ((mProxyConnection != null) && (mConnection != null)) {
// Already bound no need to bind again, just download the new file.
- IoThread.getHandler().post(mPacDownloader);
+ mNetThreadHandler.post(mPacDownloader);
return;
}
mConnection = new ServiceConnection() {
@@ -297,7 +323,7 @@
} catch (RemoteException e) {
Log.e(TAG, "Unable to reach ProxyService - PAC will not be started", e);
}
- IoThread.getHandler().post(mPacDownloader);
+ mNetThreadHandler.post(mPacDownloader);
}
}
}
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index e7fd3d8..0ec48b9 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -33,8 +33,11 @@
import android.opengl.Matrix;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings.Secure;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
import android.util.MathUtils;
import android.util.Slog;
import android.view.animation.AnimationUtils;
@@ -44,7 +47,9 @@
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
+import com.android.server.vr.VrManagerService;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Calendar;
import java.util.TimeZone;
@@ -83,6 +88,31 @@
private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
private final Handler mHandler;
+ private final AtomicBoolean mIgnoreAllColorMatrixChanges = new AtomicBoolean();
+ private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+ @Override
+ public void onVrStateChanged(final boolean enabled) {
+ // Turn off all night mode display stuff while device is in VR mode.
+ mIgnoreAllColorMatrixChanges.set(enabled);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Cancel in-progress animations
+ if (mColorMatrixAnimator != null) {
+ mColorMatrixAnimator.cancel();
+ }
+
+ final DisplayTransformManager dtm =
+ getLocalService(DisplayTransformManager.class);
+ if (enabled) {
+ dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, MATRIX_IDENTITY);
+ } else if (mController.isActivated()) {
+ dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, MATRIX_NIGHT);
+ }
+ }
+ });
+ }
+ };
private int mCurrentUser = UserHandle.USER_NULL;
private ContentObserver mUserSetupObserver;
@@ -105,7 +135,17 @@
@Override
public void onBootPhase(int phase) {
- if (phase == PHASE_BOOT_COMPLETED) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ IVrManager vrManager =
+ (IVrManager) getBinderService(VrManagerService.VR_MANAGER_BINDER_SERVICE);
+ if (vrManager != null) {
+ try {
+ vrManager.registerListener(mVrStateCallbacks);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register VR mode state listener: " + e);
+ }
+ }
+ } else if (phase == PHASE_BOOT_COMPLETED) {
mBootCompleted = true;
// Register listeners now that boot is complete.
@@ -234,6 +274,11 @@
mColorMatrixAnimator.cancel();
}
+ // Don't do any color matrix change animations if we are ignoring them anyway.
+ if (mIgnoreAllColorMatrixChanges.get()) {
+ return;
+ }
+
final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
final float[] to = mIsActivated ? MATRIX_NIGHT : null;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 2d71ce9..ede6e30 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -108,7 +108,7 @@
private CharSequence mQueueTitle;
private int mRatingType;
private int mRepeatMode;
- private boolean mShuffleMode;
+ private boolean mShuffleModeEnabled;
private long mLastActiveTime;
// End TransportPerformer fields
@@ -649,7 +649,7 @@
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
ISessionControllerCallback cb = mControllerCallbacks.get(i);
try {
- cb.onShuffleModeChanged(mShuffleMode);
+ cb.onShuffleModeChanged(mShuffleModeEnabled);
} catch (DeadObjectException e) {
mControllerCallbacks.remove(i);
Log.w(TAG, "Removed dead callback in pushShuffleModeUpdate.", e);
@@ -880,11 +880,11 @@
}
@Override
- public void setShuffleMode(boolean shuffleMode) {
+ public void setShuffleModeEnabled(boolean enabled) {
boolean changed;
synchronized (mLock) {
- changed = mShuffleMode != shuffleMode;
- mShuffleMode = shuffleMode;
+ changed = mShuffleModeEnabled != enabled;
+ mShuffleModeEnabled = enabled;
}
if (changed) {
mHandler.post(MessageHandler.MSG_UPDATE_SHUFFLE_MODE);
@@ -1115,9 +1115,9 @@
}
}
- public void shuffleMode(boolean shuffleMode) {
+ public void shuffleMode(boolean enabled) {
try {
- mCb.onShuffleMode(shuffleMode);
+ mCb.onShuffleMode(enabled);
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in shuffleMode.", e);
}
@@ -1364,9 +1364,9 @@
}
@Override
- public void shuffleMode(boolean shuffleMode) throws RemoteException {
+ public void shuffleMode(boolean enabled) throws RemoteException {
updateCallingPackage();
- mSessionCb.shuffleMode(shuffleMode);
+ mSessionCb.shuffleMode(enabled);
}
@@ -1419,8 +1419,8 @@
}
@Override
- public boolean getShuffleMode() {
- return mShuffleMode;
+ public boolean isShuffleModeEnabled() {
+ return mShuffleModeEnabled;
}
@Override
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index cdecd2c..1eef3a7 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -92,6 +92,7 @@
import android.Manifest;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -143,6 +144,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -166,7 +168,6 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageMonitor;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
@@ -290,6 +291,7 @@
private static final int MSG_UPDATE_INTERFACE_QUOTA = 10;
private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
private static final int MSG_POLICIES_CHANGED = 13;
+ private static final int MSG_SET_FIREWALL_RULES = 14;
private final Context mContext;
private final IActivityManager mActivityManager;
@@ -399,7 +401,6 @@
private final AppOpsManager mAppOps;
- private final MyPackageMonitor mPackageMonitor;
private final IPackageManager mIPm;
@@ -442,8 +443,6 @@
mAppOps = context.getSystemService(AppOpsManager.class);
- mPackageMonitor = new MyPackageMonitor();
-
// Expose private service for system components to use.
LocalServices.addService(NetworkPolicyManagerInternal.class,
new NetworkPolicyManagerInternalImpl());
@@ -566,117 +565,125 @@
}
public void systemReady() {
- if (!isBandwidthControlEnabled()) {
- Slog.w(TAG, "bandwidth controls disabled, unable to enforce policy");
- return;
- }
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "systemReady");
+ try {
+ if (!isBandwidthControlEnabled()) {
+ Slog.w(TAG, "bandwidth controls disabled, unable to enforce policy");
+ return;
+ }
- mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+ mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
- mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
-
- synchronized (mUidRulesFirstLock) {
- synchronized (mNetworkPoliciesSecondLock) {
- updatePowerSaveWhitelistUL();
- mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
- mPowerManagerInternal.registerLowPowerModeObserver(
- new PowerManagerInternal.LowPowerModeListener() {
- @Override
- public void onLowPowerModeChanged(boolean enabled) {
- if (LOGD) Slog.d(TAG, "onLowPowerModeChanged(" + enabled + ")");
- synchronized (mUidRulesFirstLock) {
- if (mRestrictPower != enabled) {
- mRestrictPower = enabled;
- updateRulesForRestrictPowerUL();
+ synchronized (mUidRulesFirstLock) {
+ synchronized (mNetworkPoliciesSecondLock) {
+ updatePowerSaveWhitelistUL();
+ mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ mPowerManagerInternal.registerLowPowerModeObserver(
+ new PowerManagerInternal.LowPowerModeListener() {
+ @Override
+ public void onLowPowerModeChanged(boolean enabled) {
+ if (LOGD) Slog.d(TAG, "onLowPowerModeChanged(" + enabled + ")");
+ synchronized (mUidRulesFirstLock) {
+ if (mRestrictPower != enabled) {
+ mRestrictPower = enabled;
+ updateRulesForRestrictPowerUL();
+ }
}
}
+ });
+ mRestrictPower = mPowerManagerInternal.getLowPowerModeEnabled();
+
+ mSystemReady = true;
+
+ // read policy from disk
+ readPolicyAL();
+
+ if (addDefaultRestrictBackgroundWhitelistUidsUL()) {
+ writePolicyAL();
}
- });
- mRestrictPower = mPowerManagerInternal.getLowPowerModeEnabled();
- mSystemReady = true;
-
- // read policy from disk
- readPolicyAL();
-
- if (addDefaultRestrictBackgroundWhitelistUidsUL()) {
- writePolicyAL();
+ setRestrictBackgroundUL(mRestrictBackground);
+ updateRulesForGlobalChangeAL(false);
+ updateNotificationsNL();
}
-
- setRestrictBackgroundUL(mRestrictBackground);
- updateRulesForGlobalChangeAL(false);
- updateNotificationsNL();
}
+
+ try {
+ mActivityManager.registerUidObserver(mUidObserver,
+ ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE);
+ mNetworkManager.registerObserver(mAlertObserver);
+ } catch (RemoteException e) {
+ // ignored; both services live in system_server
+ }
+
+ // listen for changes to power save whitelist
+ final IntentFilter whitelistFilter = new IntentFilter(
+ PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+ mContext.registerReceiver(mPowerSaveWhitelistReceiver, whitelistFilter, null, mHandler);
+
+ DeviceIdleController.LocalService deviceIdleService
+ = LocalServices.getService(DeviceIdleController.LocalService.class);
+ deviceIdleService.setNetworkPolicyTempWhitelistCallback(mTempPowerSaveChangedCallback);
+
+ // watch for network interfaces to be claimed
+ final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
+ mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler);
+
+ // listen for package changes to update policy
+ final IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(ACTION_PACKAGE_ADDED);
+ packageFilter.addDataScheme("package");
+ mContext.registerReceiver(mPackageReceiver, packageFilter, null, mHandler);
+
+ // listen for UID changes to update policy
+ mContext.registerReceiver(
+ mUidRemovedReceiver, new IntentFilter(ACTION_UID_REMOVED), null, mHandler);
+
+ // listen for user changes to update policy
+ final IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(ACTION_USER_ADDED);
+ userFilter.addAction(ACTION_USER_REMOVED);
+ mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
+
+ // listen for stats update events
+ final IntentFilter statsFilter = new IntentFilter(ACTION_NETWORK_STATS_UPDATED);
+ mContext.registerReceiver(
+ mStatsReceiver, statsFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
+
+ // listen for restrict background changes from notifications
+ final IntentFilter allowFilter = new IntentFilter(ACTION_ALLOW_BACKGROUND);
+ mContext.registerReceiver(mAllowReceiver, allowFilter, MANAGE_NETWORK_POLICY, mHandler);
+
+ // listen for snooze warning from notifications
+ final IntentFilter snoozeWarningFilter = new IntentFilter(ACTION_SNOOZE_WARNING);
+ mContext.registerReceiver(mSnoozeWarningReceiver, snoozeWarningFilter,
+ MANAGE_NETWORK_POLICY, mHandler);
+
+ // listen for configured wifi networks to be removed
+ final IntentFilter wifiConfigFilter =
+ new IntentFilter(CONFIGURED_NETWORKS_CHANGED_ACTION);
+ mContext.registerReceiver(mWifiConfigReceiver, wifiConfigFilter, null, mHandler);
+
+ // listen for wifi state changes to catch metered hint
+ final IntentFilter wifiStateFilter = new IntentFilter(
+ WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mContext.registerReceiver(mWifiStateReceiver, wifiStateFilter, null, mHandler);
+
+ mUsageStats.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
-
- try {
- mActivityManager.registerUidObserver(mUidObserver,
- ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE);
- mNetworkManager.registerObserver(mAlertObserver);
- } catch (RemoteException e) {
- // ignored; both services live in system_server
- }
-
- // listen for changes to power save whitelist
- final IntentFilter whitelistFilter = new IntentFilter(
- PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
- mContext.registerReceiver(mPowerSaveWhitelistReceiver, whitelistFilter, null, mHandler);
-
- DeviceIdleController.LocalService deviceIdleService
- = LocalServices.getService(DeviceIdleController.LocalService.class);
- deviceIdleService.setNetworkPolicyTempWhitelistCallback(mTempPowerSaveChangedCallback);
-
- // watch for network interfaces to be claimed
- final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
- mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler);
-
- // listen for package changes to update policy
- final IntentFilter packageFilter = new IntentFilter();
- packageFilter.addAction(ACTION_PACKAGE_ADDED);
- packageFilter.addDataScheme("package");
- mContext.registerReceiver(mPackageReceiver, packageFilter, null, mHandler);
-
- // listen for UID changes to update policy
- mContext.registerReceiver(
- mUidRemovedReceiver, new IntentFilter(ACTION_UID_REMOVED), null, mHandler);
-
- // listen for user changes to update policy
- final IntentFilter userFilter = new IntentFilter();
- userFilter.addAction(ACTION_USER_ADDED);
- userFilter.addAction(ACTION_USER_REMOVED);
- mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
-
- // listen for stats update events
- final IntentFilter statsFilter = new IntentFilter(ACTION_NETWORK_STATS_UPDATED);
- mContext.registerReceiver(
- mStatsReceiver, statsFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
-
- // listen for restrict background changes from notifications
- final IntentFilter allowFilter = new IntentFilter(ACTION_ALLOW_BACKGROUND);
- mContext.registerReceiver(mAllowReceiver, allowFilter, MANAGE_NETWORK_POLICY, mHandler);
-
- // listen for snooze warning from notifications
- final IntentFilter snoozeWarningFilter = new IntentFilter(ACTION_SNOOZE_WARNING);
- mContext.registerReceiver(mSnoozeWarningReceiver, snoozeWarningFilter,
- MANAGE_NETWORK_POLICY, mHandler);
-
- // listen for configured wifi networks to be removed
- final IntentFilter wifiConfigFilter = new IntentFilter(CONFIGURED_NETWORKS_CHANGED_ACTION);
- mContext.registerReceiver(mWifiConfigReceiver, wifiConfigFilter, null, mHandler);
-
- // listen for wifi state changes to catch metered hint
- final IntentFilter wifiStateFilter = new IntentFilter(
- WifiManager.NETWORK_STATE_CHANGED_ACTION);
- mContext.registerReceiver(mWifiStateReceiver, wifiStateFilter, null, mHandler);
-
- mUsageStats.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
-
}
final private IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
- synchronized (mUidRulesFirstLock) {
- updateUidStateUL(uid, procState);
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
+ try {
+ synchronized (mUidRulesFirstLock) {
+ updateUidStateUL(uid, procState);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@@ -700,6 +707,7 @@
synchronized (mUidRulesFirstLock) {
updatePowerSaveWhitelistUL();
updateRulesForRestrictPowerUL();
+ updateRulesForAppIdleUL();
}
}
};
@@ -747,6 +755,7 @@
if (LOGV) Slog.v(TAG, "ACTION_UID_REMOVED for uid=" + uid);
synchronized (mUidRulesFirstLock) {
mUidPolicy.delete(uid);
+ removeRestrictBackgroundWhitelistedUidUL(uid, true, true);
updateRestrictionRulesForUidUL(uid);
synchronized (mNetworkPoliciesSecondLock) {
writePolicyAL();
@@ -1429,8 +1438,14 @@
+ "; generating default policy");
// Build default mobile policy, and assume usage cycle starts today
- final long warningBytes = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_networkPolicyDefaultWarning) * MB_IN_BYTES;
+ final int dataWarningConfig = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_networkPolicyDefaultWarning);
+ final long warningBytes;
+ if (dataWarningConfig == WARNING_DISABLED) {
+ warningBytes = WARNING_DISABLED;
+ } else {
+ warningBytes = dataWarningConfig * MB_IN_BYTES;
+ }
final Time time = new Time();
time.setToNow();
@@ -2049,7 +2064,7 @@
// Must whitelist foreground apps before turning data saver mode on.
// TODO: there is no need to iterate through all apps here, just those in the foreground,
// so it could call AM to get the UIDs of such apps, and iterate through them instead.
- updateRulesForAllAppsUL(TYPE_RESTRICT_BACKGROUND);
+ updateRulesForRestrictBackgroundUL();
try {
if (!mNetworkManager.setDataSaverModeEnabled(mRestrictBackground)) {
Slog.e(TAG, "Could not change Data Saver Mode on NMS to " + mRestrictBackground);
@@ -2144,21 +2159,26 @@
@Override
public void setDeviceIdleMode(boolean enabled) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
-
- synchronized (mUidRulesFirstLock) {
- if (mDeviceIdleMode != enabled) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setDeviceIdleMode");
+ try {
+ synchronized (mUidRulesFirstLock) {
+ if (mDeviceIdleMode == enabled) {
+ return;
+ }
mDeviceIdleMode = enabled;
if (mSystemReady) {
// Device idle change means we need to rebuild rules for all
// known apps, so do a global refresh.
updateRulesForRestrictPowerUL();
}
- if (enabled) {
- EventLogTags.writeDeviceIdleOnPhase("net");
- } else {
- EventLogTags.writeDeviceIdleOffPhase("net");
- }
}
+ if (enabled) {
+ EventLogTags.writeDeviceIdleOnPhase("net");
+ } else {
+ EventLogTags.writeDeviceIdleOffPhase("net");
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@@ -2434,25 +2454,30 @@
* {@link #updateRulesForPowerRestrictionsUL(int)}
*/
private void updateUidStateUL(int uid, int uidState) {
- final int oldUidState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
- if (oldUidState != uidState) {
- // state changed, push updated rules
- mUidState.put(uid, uidState);
- updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, uidState);
- if (isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
- != isProcStateAllowedWhileIdleOrPowerSaveMode(uidState) ) {
- if (isUidIdle(uid)) {
- updateRuleForAppIdleUL(uid);
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateUidStateUL");
+ try {
+ final int oldUidState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ if (oldUidState != uidState) {
+ // state changed, push updated rules
+ mUidState.put(uid, uidState);
+ updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, uidState);
+ if (isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
+ != isProcStateAllowedWhileIdleOrPowerSaveMode(uidState) ) {
+ if (isUidIdle(uid)) {
+ updateRuleForAppIdleUL(uid);
+ }
+ if (mDeviceIdleMode) {
+ updateRuleForDeviceIdleUL(uid);
+ }
+ if (mRestrictPower) {
+ updateRuleForRestrictPowerUL(uid);
+ }
+ updateRulesForPowerRestrictionsUL(uid);
}
- if (mDeviceIdleMode) {
- updateRuleForDeviceIdleUL(uid);
- }
- if (mRestrictPower) {
- updateRuleForRestrictPowerUL(uid);
- }
- updateRulesForPowerRestrictionsUL(uid);
+ updateNetworkStats(uid, isUidStateForegroundUL(uidState));
}
- updateNetworkStats(uid, isUidStateForegroundUL(uidState));
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@@ -2505,8 +2530,13 @@
}
void updateRulesForPowerSaveUL() {
- updateRulesForWhitelistedPowerSaveUL(mRestrictPower, FIREWALL_CHAIN_POWERSAVE,
- mUidFirewallPowerSaveRules);
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForPowerSaveUL");
+ try {
+ updateRulesForWhitelistedPowerSaveUL(mRestrictPower, FIREWALL_CHAIN_POWERSAVE,
+ mUidFirewallPowerSaveRules);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
}
void updateRuleForRestrictPowerUL(int uid) {
@@ -2514,8 +2544,13 @@
}
void updateRulesForDeviceIdleUL() {
- updateRulesForWhitelistedPowerSaveUL(mDeviceIdleMode, FIREWALL_CHAIN_DOZABLE,
- mUidFirewallDozableRules);
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForDeviceIdleUL");
+ try {
+ updateRulesForWhitelistedPowerSaveUL(mDeviceIdleMode, FIREWALL_CHAIN_DOZABLE,
+ mUidFirewallDozableRules);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
}
void updateRuleForDeviceIdleUL(int uid) {
@@ -2552,10 +2587,10 @@
uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
}
}
- setUidFirewallRules(chain, uidRules);
+ setUidFirewallRulesAsync(chain, uidRules, CHAIN_TOGGLE_ENABLE);
+ } else {
+ setUidFirewallRulesAsync(chain, null, CHAIN_TOGGLE_DISABLE);
}
-
- enableFirewallChainUL(chain, enabled);
}
private boolean isWhitelistedBatterySaverUL(int uid) {
@@ -2577,27 +2612,32 @@
}
void updateRulesForAppIdleUL() {
- final SparseIntArray uidRules = mUidFirewallStandbyRules;
- uidRules.clear();
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForAppIdleUL");
+ try {
+ final SparseIntArray uidRules = mUidFirewallStandbyRules;
+ uidRules.clear();
- // Fully update the app idle firewall chain.
- final List<UserInfo> users = mUserManager.getUsers();
- for (int ui = users.size() - 1; ui >= 0; ui--) {
- UserInfo user = users.get(ui);
- int[] idleUids = mUsageStats.getIdleUidsForUser(user.id);
- for (int uid : idleUids) {
- if (!mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid), false)) {
- // quick check: if this uid doesn't have INTERNET permission, it
- // doesn't have network access anyway, so it is a waste to mess
- // with it here.
- if (hasInternetPermissions(uid)) {
- uidRules.put(uid, FIREWALL_RULE_DENY);
+ // Fully update the app idle firewall chain.
+ final List<UserInfo> users = mUserManager.getUsers();
+ for (int ui = users.size() - 1; ui >= 0; ui--) {
+ UserInfo user = users.get(ui);
+ int[] idleUids = mUsageStats.getIdleUidsForUser(user.id);
+ for (int uid : idleUids) {
+ if (!mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid), false)) {
+ // quick check: if this uid doesn't have INTERNET permission, it
+ // doesn't have network access anyway, so it is a waste to mess
+ // with it here.
+ if (hasInternetPermissions(uid)) {
+ uidRules.put(uid, FIREWALL_RULE_DENY);
+ }
}
}
}
- }
- setUidFirewallRules(FIREWALL_CHAIN_STANDBY, uidRules);
+ setUidFirewallRulesAsync(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
}
void updateRuleForAppIdleUL(int uid) {
@@ -2622,33 +2662,41 @@
* {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value.
*/
private void updateRulesForGlobalChangeAL(boolean restrictedNetworksChanged) {
- long start;
- if (LOGD) start = System.currentTimeMillis();
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForGlobalChangeAL");
+ try {
+ updateRulesForAppIdleUL();
+ updateRulesForRestrictPowerUL();
+ updateRulesForRestrictBackgroundUL();
- updateRulesForRestrictPowerUL();
- updateRulesForRestrictBackgroundUL();
-
- // If the set of restricted networks may have changed, re-evaluate those.
- if (restrictedNetworksChanged) {
- normalizePoliciesNL();
- updateNetworkRulesNL();
- }
- if (LOGD) {
- final long delta = System.currentTimeMillis() - start;
- Slog.d(TAG, "updateRulesForGlobalChangeAL(" + restrictedNetworksChanged + ") took "
- + delta + "ms");
+ // If the set of restricted networks may have changed, re-evaluate those.
+ if (restrictedNetworksChanged) {
+ normalizePoliciesNL();
+ updateNetworkRulesNL();
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
+ // TODO: rename / document to make it clear these are global (not app-specific) rules
private void updateRulesForRestrictPowerUL() {
- updateRulesForDeviceIdleUL();
- updateRulesForAppIdleUL();
- updateRulesForPowerSaveUL();
- updateRulesForAllAppsUL(TYPE_RESTRICT_POWER);
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForRestrictPowerUL");
+ try {
+ updateRulesForDeviceIdleUL();
+ updateRulesForPowerSaveUL();
+ updateRulesForAllAppsUL(TYPE_RESTRICT_POWER);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
}
private void updateRulesForRestrictBackgroundUL() {
- updateRulesForAllAppsUL(TYPE_RESTRICT_BACKGROUND);
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForRestrictBackgroundUL");
+ try {
+ updateRulesForAllAppsUL(TYPE_RESTRICT_BACKGROUND);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
}
private static final int TYPE_RESTRICT_BACKGROUND = 1;
@@ -2663,33 +2711,42 @@
// TODO: refactor / consolidate all those updateXyz methods, there are way too many of them...
private void updateRulesForAllAppsUL(@RestrictType int type) {
- final PackageManager pm = mContext.getPackageManager();
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForRestrictPowerUL-" + type);
+ }
+ try {
+ final PackageManager pm = mContext.getPackageManager();
- // update rules for all installed applications
- final List<UserInfo> users = mUserManager.getUsers();
- final List<ApplicationInfo> apps = pm.getInstalledApplications(
- PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ // update rules for all installed applications
+ final List<UserInfo> users = mUserManager.getUsers();
+ final List<ApplicationInfo> apps = pm.getInstalledApplications(
+ PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
- final int usersSize = users.size();
- final int appsSize = apps.size();
- for (int i = 0; i < usersSize; i++) {
- final UserInfo user = users.get(i);
- for (int j = 0; j < appsSize; j++) {
- final ApplicationInfo app = apps.get(j);
- final int uid = UserHandle.getUid(user.id, app.uid);
- switch (type) {
- case TYPE_RESTRICT_BACKGROUND:
- updateRulesForDataUsageRestrictionsUL(uid);
- break;
- case TYPE_RESTRICT_POWER:
- updateRulesForPowerRestrictionsUL(uid);
- break;
- default:
- Slog.w(TAG, "Invalid type for updateRulesForAllApps: " + type);
+ final int usersSize = users.size();
+ final int appsSize = apps.size();
+ for (int i = 0; i < usersSize; i++) {
+ final UserInfo user = users.get(i);
+ for (int j = 0; j < appsSize; j++) {
+ final ApplicationInfo app = apps.get(j);
+ final int uid = UserHandle.getUid(user.id, app.uid);
+ switch (type) {
+ case TYPE_RESTRICT_BACKGROUND:
+ updateRulesForDataUsageRestrictionsUL(uid);
+ break;
+ case TYPE_RESTRICT_POWER:
+ updateRulesForPowerRestrictionsUL(uid);
+ break;
+ default:
+ Slog.w(TAG, "Invalid type for updateRulesForAllApps: " + type);
+ }
}
}
+ } finally {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
}
}
@@ -3183,6 +3240,18 @@
removeInterfaceQuota((String) msg.obj);
return true;
}
+ case MSG_SET_FIREWALL_RULES: {
+ final int chain = msg.arg1;
+ final int toggle = msg.arg2;
+ final SparseIntArray uidRules = (SparseIntArray) msg.obj;
+ if (uidRules != null) {
+ setUidFirewallRules(chain, uidRules);
+ }
+ if (toggle != CHAIN_TOGGLE_NONE) {
+ enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
+ }
+ return true;
+ }
default: {
return false;
}
@@ -3248,6 +3317,31 @@
}
}
+ private static final int CHAIN_TOGGLE_NONE = 0;
+ private static final int CHAIN_TOGGLE_ENABLE = 1;
+ private static final int CHAIN_TOGGLE_DISABLE = 2;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, value = {
+ CHAIN_TOGGLE_NONE,
+ CHAIN_TOGGLE_ENABLE,
+ CHAIN_TOGGLE_DISABLE
+ })
+ public @interface ChainToggleType {
+ }
+
+ /**
+ * Calls {@link #setUidFirewallRules(int, SparseIntArray)} and
+ * {@link #enableFirewallChainUL(int, boolean)} asynchronously.
+ *
+ * @param chain firewall chain.
+ * @param uidRules new UID rules; if {@code null}, only toggles chain state.
+ * @param toggle whether the chain should be enabled, disabled, or not changed.
+ */
+ private void setUidFirewallRulesAsync(int chain, @Nullable SparseIntArray uidRules,
+ @ChainToggleType int toggle) {
+ mHandler.obtainMessage(MSG_SET_FIREWALL_RULES, chain, toggle, uidRules).sendToTarget();
+ }
+
/**
* Set uid rules on a particular firewall chain. This is going to synchronize the rules given
* here to netd. It will clean up dead rules and make sure the target chain only contains rules
@@ -3418,18 +3512,6 @@
}
}
- private class MyPackageMonitor extends PackageMonitor {
-
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- if (LOGV) Slog.v(TAG, "onPackageRemoved: " + packageName + " ->" + uid);
- synchronized (mUidRulesFirstLock) {
- removeRestrictBackgroundWhitelistedUidUL(uid, true, true);
- updateRestrictionRulesForUidUL(uid);
- }
- }
- }
-
private class NetworkPolicyManagerInternalImpl extends NetworkPolicyManagerInternal {
@Override
diff --git a/services/core/java/com/android/server/notification/ImportanceExtractor.java b/services/core/java/com/android/server/notification/ImportanceExtractor.java
index 885b9b7..3bdc22c 100644
--- a/services/core/java/com/android/server/notification/ImportanceExtractor.java
+++ b/services/core/java/com/android/server/notification/ImportanceExtractor.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import android.app.NotificationManager;
import android.content.Context;
import android.util.Slog;
@@ -41,10 +42,17 @@
if (DBG) Slog.d(TAG, "missing config");
return null;
}
-
- record.setUserImportance(
- mConfig.getImportance(record.sbn.getPackageName(), record.sbn.getUid()));
-
+ int importance = NotificationManager.IMPORTANCE_UNSPECIFIED;
+ int appImportance = mConfig.getImportance(
+ record.sbn.getPackageName(), record.sbn.getUid());
+ int channelImportance = record.getChannel().getImportance();
+ if (appImportance == NotificationManager.IMPORTANCE_UNSPECIFIED) {
+ record.setUserImportance(channelImportance);
+ } else if (channelImportance == NotificationManager.IMPORTANCE_UNSPECIFIED) {
+ record.setUserImportance(appImportance);
+ } else {
+ record.setUserImportance(Math.min(appImportance, channelImportance));
+ }
return null;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 084ba8e..6b2df73 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -18,6 +18,7 @@
import static android.service.notification.NotificationRankerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationRankerService.REASON_APP_CANCEL_ALL;
+import static android.service.notification.NotificationRankerService.REASON_CHANNEL_BANNED;
import static android.service.notification.NotificationRankerService.REASON_DELEGATE_CANCEL;
import static android.service.notification.NotificationRankerService.REASON_DELEGATE_CANCEL_ALL;
import static android.service.notification.NotificationRankerService.REASON_DELEGATE_CLICK;
@@ -55,6 +56,7 @@
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
@@ -746,8 +748,8 @@
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
if (cancelNotifications) {
- cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, 0, 0, !queryRestart,
- changeUserId, reason, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, null, 0, 0,
+ !queryRestart, changeUserId, reason, null);
}
}
}
@@ -779,13 +781,13 @@
} else if (action.equals(Intent.ACTION_USER_STOPPED)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0) {
- cancelAllNotificationsInt(MY_UID, MY_PID, null, 0, 0, true, userHandle,
+ cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
REASON_USER_STOPPED, null);
}
} else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0) {
- cancelAllNotificationsInt(MY_UID, MY_PID, null, 0, 0, true, userHandle,
+ cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
REASON_PROFILE_TURNED_OFF, null);
}
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
@@ -907,6 +909,28 @@
}
@VisibleForTesting
+ void setStatusBarManager(StatusBarManagerInternal statusBar) {
+ mStatusBar = statusBar;
+ }
+
+ @VisibleForTesting
+ void setLights(Light light) {
+ mNotificationLight = light;
+ mAttentionLight = light;
+ }
+
+ @VisibleForTesting
+ void setScreenOn(boolean on) {
+ mScreenOn = on;
+ }
+
+ @VisibleForTesting
+ void addNotification(NotificationRecord r) {
+ mNotificationList.add(r);
+ mNotificationsByKey.put(r.sbn.getKey(), r);
+ }
+
+ @VisibleForTesting
void setSystemReady(boolean systemReady) {
mSystemReady = systemReady;
}
@@ -1149,8 +1173,8 @@
// Now, cancel any outstanding notifications that are part of a just-disabled app
if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, 0, 0, true, UserHandle.getUserId(uid),
- REASON_PACKAGE_BANNED, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
+ UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
}
}
@@ -1408,7 +1432,7 @@
// Calling from user space, don't allow the canceling of actively
// running foreground services.
cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
- pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId,
+ pkg, null, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId,
REASON_APP_CANCEL_ALL, null);
}
@@ -1486,6 +1510,87 @@
return mRankingHelper.getImportance(pkg, uid);
}
+ @Override
+ public void createNotificationChannel(String pkg, NotificationChannel channel) {
+ Preconditions.checkNotNull(channel);
+ Preconditions.checkNotNull(channel.getId());
+ Preconditions.checkNotNull(channel.getName());
+ checkCallerIsSystemOrSameApp(pkg);
+ mRankingHelper.createNotificationChannel(pkg, Binder.getCallingUid(), channel);
+ savePolicyFile();
+ }
+
+ @Override
+ public void updateNotificationChannel(String pkg, NotificationChannel channel) {
+ Preconditions.checkNotNull(channel);
+ Preconditions.checkNotNull(channel.getId());
+ checkCallerIsSystemOrSameApp(pkg);
+ if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+ // cancel
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null);
+ }
+ mRankingHelper.updateNotificationChannel(Binder.getCallingUid(), pkg,
+ Binder.getCallingUid(), channel);
+ savePolicyFile();
+ }
+
+ @Override
+ public NotificationChannel getNotificationChannel(String pkg, String channelId) {
+ Preconditions.checkNotNull(channelId);
+ checkCallerIsSystemOrSameApp(pkg);
+ return mRankingHelper.getNotificationChannel(pkg, Binder.getCallingUid(), channelId);
+ }
+
+ @Override
+ public NotificationChannel getNotificationChannelForPackage(String pkg, int uid,
+ String channelId) {
+ Preconditions.checkNotNull(channelId);
+ checkCallerIsSystem();
+ return mRankingHelper.getNotificationChannel(pkg, uid, channelId);
+ }
+
+ @Override
+ public void deleteNotificationChannel(String pkg, String channelId) {
+ Preconditions.checkNotNull(channelId);
+ checkCallerIsSystemOrSameApp(pkg);
+ if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
+ throw new IllegalArgumentException("Cannot delete default channel");
+ }
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null);
+ mRankingHelper.deleteNotificationChannel(pkg, Binder.getCallingUid(), channelId);
+ savePolicyFile();
+ }
+
+ @Override
+ public void updateNotificationChannelForPackage(String pkg, int uid,
+ NotificationChannel channel) {
+ Preconditions.checkNotNull(channel);
+ Preconditions.checkNotNull(channel.getId());
+ checkCallerIsSystem();
+ if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+ // cancel
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null);
+ }
+ mRankingHelper.updateNotificationChannel(Binder.getCallingUid(), pkg, uid, channel);
+ savePolicyFile();
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannel> getNotificationChannelsForPackage(String pkg,
+ int uid) {
+ checkCallerIsSystem();
+ return mRankingHelper.getNotificationChannels(pkg, uid);
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg) {
+ checkCallerIsSystemOrSameApp(pkg);
+ return mRankingHelper.getNotificationChannels(pkg, Binder.getCallingUid());
+ }
+
/**
* System-only API for getting a list of current (i.e. not cleared) notifications.
*
@@ -1955,6 +2060,29 @@
});
}
+ @Override
+ public void requestUnbindProvider(IConditionProvider provider) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ // allow bound services to disable themselves
+ final ManagedServiceInfo info = mConditionProviders.checkServiceToken(provider);
+ info.getOwner().setComponentState(info.component, false);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void requestBindProvider(ComponentName component) {
+ checkCallerIsSystemOrSameApp(component.getPackageName());
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mConditionProviders.setComponentState(component, true);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
private void enforceSystemOrSystemUIOrVolume(String message) {
if (mAudioManagerInternal != null) {
final int vcuid = mAudioManagerInternal.getVolumeControllerUid();
@@ -2312,7 +2440,10 @@
summaryNotification, adjustedSbn.getUser(),
newAutoBundleKey,
System.currentTimeMillis());
- summaryRecord = new NotificationRecord(getContext(), summarySbn);
+ summaryRecord = new NotificationRecord(getContext(), summarySbn,
+ mRankingHelper.getNotificationChannel(adjustedSbn.getPackageName(),
+ adjustedSbn.getUid(),
+ adjustedSbn.getNotification().getNotificationChannel()));
summaries.put(adjustment.getPackage(), summarySbn.getKey());
}
}
@@ -2616,7 +2747,9 @@
Notification.PRIORITY_MAX);
// setup local book-keeping
- final NotificationRecord r = new NotificationRecord(getContext(), n);
+ final NotificationRecord r = new NotificationRecord(getContext(), n,
+ mRankingHelper.getNotificationChannel(pkg, callingUid,
+ n.getNotification().getNotificationChannel()));
mHandler.post(new EnqueueNotificationRunnable(userId, r));
idOut[0] = id;
@@ -2674,7 +2807,8 @@
final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
// blocked apps
- if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE
+ if (r.getImportance() == NotificationManager.IMPORTANCE_NONE
+ || r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE
|| !noteNotificationOp(pkg, callingUid) || isPackageSuspended) {
if (!isSystemNotification) {
if (isPackageSuspended) {
@@ -2842,9 +2976,8 @@
// DEFAULT_SOUND or because notification.sound is pointing at
// Settings.System.NOTIFICATION_SOUND)
final boolean useDefaultSound =
- (notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
- Settings.System.DEFAULT_NOTIFICATION_URI
- .equals(notification.sound);
+ (notification.defaults & Notification.DEFAULT_SOUND) != 0
+ || Settings.System.DEFAULT_NOTIFICATION_URI.equals(notification.sound);
Uri soundUri = null;
if (useDefaultSound) {
@@ -2855,6 +2988,9 @@
} else if (notification.sound != null) {
soundUri = notification.sound;
hasValidSound = (soundUri != null);
+ } else if (record.getChannel().getDefaultRingtone() != null) {
+ soundUri = record.getChannel().getDefaultRingtone();
+ hasValidSound = (soundUri != null);
}
// Does the notification want to specify its own vibration?
@@ -2871,8 +3007,10 @@
final boolean useDefaultVibrate =
(notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
+ final boolean hasChannelVibration = record.getChannel().shouldVibrate();
+
hasValidVibrate = useDefaultVibrate || convertSoundToVibration ||
- hasCustomVibrate;
+ hasCustomVibrate || hasChannelVibration;
// We can alert, and we're allowed to alert, but if the developer asked us to only do
// it once, and we already have, then don't.
@@ -2908,26 +3046,13 @@
}
}
}
-
if (hasValidVibrate && !(mAudioManager.getRingerModeInternal()
== AudioManager.RINGER_MODE_SILENT)) {
mVibrateNotificationKey = key;
if (useDefaultVibrate || convertSoundToVibration) {
- // Escalate privileges so we can use the vibrator even if the
- // notifying app does not have the VIBRATE permission.
- long identity = Binder.clearCallingIdentity();
- try {
- mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
- useDefaultVibrate ? mDefaultVibrationPattern
- : mFallbackVibrationPattern,
- ((notification.flags & Notification.FLAG_INSISTENT) != 0)
- ? 0: -1, audioAttributesForNotification(notification));
- buzz = true;
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- } else if (notification.vibrate.length > 1) {
+ playNonCustomVibration(record, useDefaultVibrate);
+ } else if (notification.vibrate != null && notification.vibrate.length > 1) {
// If you want your own vibration pattern, you need the VIBRATE
// permission
mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
@@ -2935,6 +3060,8 @@
((notification.flags & Notification.FLAG_INSISTENT) != 0)
? 0: -1, audioAttributesForNotification(notification));
buzz = true;
+ } else if (hasChannelVibration) {
+ playNonCustomVibration(record, useDefaultVibrate);
}
}
}
@@ -2952,7 +3079,7 @@
// light
// release the light
boolean wasShowLights = mLights.remove(key);
- if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold
+ if (shouldShowLights(record) && aboveThreshold
&& ((record.getSuppressedVisualEffects()
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) {
mLights.add(key);
@@ -2976,6 +3103,28 @@
}
}
+ private boolean shouldShowLights(final NotificationRecord record) {
+ return record.getChannel().shouldShowLights()
+ || (record.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0;
+ }
+
+ private boolean playNonCustomVibration(final NotificationRecord record,
+ boolean useDefaultVibrate) {
+ // Escalate privileges so we can use the vibrator even if the
+ // notifying app does not have the VIBRATE permission.
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+ useDefaultVibrate ? mDefaultVibrationPattern
+ : mFallbackVibrationPattern,
+ ((record.getNotification().flags & Notification.FLAG_INSISTENT) != 0)
+ ? 0: -1, audioAttributesForNotification(record.getNotification()));
+ return true;
+ } finally{
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
private static AudioAttributes audioAttributesForNotification(Notification n) {
if (n.audioAttributes != null
&& !Notification.AUDIO_ATTRIBUTES_DEFAULT.equals(n.audioAttributes)) {
@@ -3466,8 +3615,8 @@
* Cancels all notifications from a given package that have all of the
* {@code mustHaveFlags}.
*/
- boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, int mustHaveFlags,
- int mustNotHaveFlags, boolean doit, int userId, int reason,
+ boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
+ int mustHaveFlags, int mustNotHaveFlags, boolean doit, int userId, int reason,
ManagedServiceInfo listener) {
String listenerName = listener == null ? null : listener.component.toShortString();
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
@@ -3495,6 +3644,9 @@
if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
continue;
}
+ if (channelId == null || !channelId.equals(r.getChannel().getId())) {
+ continue;
+ }
if (canceledNotifications == null) {
canceledNotifications = new ArrayList<>();
}
@@ -3613,7 +3765,8 @@
int ledARGB = ledno.ledARGB;
int ledOnMS = ledno.ledOnMS;
int ledOffMS = ledno.ledOffMS;
- if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0) {
+ if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0
+ || (ledno.flags & Notification.FLAG_SHOW_LIGHTS) == 0) {
ledARGB = mDefaultNotificationColor;
ledOnMS = mDefaultNotificationLedOn;
ledOffMS = mDefaultNotificationLedOff;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index b2198d7..c5de93e 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -23,12 +23,14 @@
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MAX;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
+import android.net.Uri;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
@@ -101,8 +103,11 @@
private String mUserExplanation;
private String mPeopleExplanation;
+ private NotificationChannel mNotificationChannel;
+
@VisibleForTesting
- public NotificationRecord(Context context, StatusBarNotification sbn)
+ public NotificationRecord(Context context, StatusBarNotification sbn,
+ NotificationChannel channel)
{
this.sbn = sbn;
mOriginalFlags = sbn.getNotification().flags;
@@ -111,6 +116,7 @@
mUpdateTimeMs = mCreationTimeMs;
mContext = context;
stats = new NotificationUsageStats.SingleNotificationStats();
+ mNotificationChannel = channel;
mImportance = defaultImportance();
}
@@ -145,7 +151,9 @@
boolean isNoisy = (n.defaults & Notification.DEFAULT_SOUND) != 0
|| (n.defaults & Notification.DEFAULT_VIBRATE) != 0
|| n.sound != null
- || n.vibrate != null;
+ || n.vibrate != null
+ || mNotificationChannel.shouldVibrate()
+ || mNotificationChannel.getDefaultRingtone() != null;
stats.isNoisy = isNoisy;
if (!isNoisy && importance > IMPORTANCE_LOW) {
@@ -283,6 +291,7 @@
pw.println(prefix + " mVisibleSinceMs=" + mVisibleSinceMs);
pw.println(prefix + " mUpdateTimeMs=" + mUpdateTimeMs);
pw.println(prefix + " mSuppressedVisualEffects= " + mSuppressedVisualEffects);
+ pw.println(prefix + " mNotificationChannel= " + mNotificationChannel);
}
@@ -527,4 +536,8 @@
public boolean isImportanceFromUser() {
return mImportance == mUserImportance;
}
+
+ public NotificationChannel getChannel() {
+ return mNotificationChannel;
+ }
}
diff --git a/services/core/java/com/android/server/notification/PriorityExtractor.java b/services/core/java/com/android/server/notification/PriorityExtractor.java
index 6c76476..666cf00d 100644
--- a/services/core/java/com/android/server/notification/PriorityExtractor.java
+++ b/services/core/java/com/android/server/notification/PriorityExtractor.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import android.app.Notification;
import android.content.Context;
import android.util.Slog;
@@ -42,8 +43,11 @@
return null;
}
- record.setPackagePriority(
- mConfig.getPriority(record.sbn.getPackageName(), record.sbn.getUid()));
+ int priority = mConfig.getPriority(record.sbn.getPackageName(), record.sbn.getUid());
+ if (priority == Notification.PRIORITY_DEFAULT && record.getChannel().canBypassDnd()){
+ priority = Notification.PRIORITY_MAX;
+ }
+ record.setPackagePriority(priority);
return null;
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index b5cc2ef..2df4043 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -15,6 +15,9 @@
*/
package com.android.server.notification;
+import android.app.NotificationChannel;
+import android.content.pm.ParceledListSlice;
+
public interface RankingConfig {
int getPriority(String packageName, int uid);
@@ -28,4 +31,10 @@
void setImportance(String packageName, int uid, int importance);
int getImportance(String packageName, int uid);
+
+ void createNotificationChannel(String pkg, int uid, NotificationChannel channel);
+ void updateNotificationChannel(int callingUid, String pkg, int uid, NotificationChannel channel);
+ NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
+ void deleteNotificationChannel(String pkg, int uid, String channelId);
+ ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid);
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 32c5b13..7182da1 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -16,16 +16,19 @@
package com.android.server.notification;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
+import android.os.Process;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService.Ranking;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
-import com.android.server.notification.NotificationManagerService.DumpFilter;
+import com.android.internal.R;
import org.json.JSONArray;
import org.json.JSONException;
@@ -38,6 +41,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -48,15 +52,15 @@
private static final String TAG_RANKING = "ranking";
private static final String TAG_PACKAGE = "package";
- private static final String ATT_VERSION = "version";
+ private static final String TAG_CHANNEL = "channel";
+ private static final String ATT_VERSION = "version";
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
+ private static final String ATT_ID = "id";
private static final String ATT_PRIORITY = "priority";
private static final String ATT_VISIBILITY = "visibility";
private static final String ATT_IMPORTANCE = "importance";
- private static final String ATT_TOPIC_ID = "id";
- private static final String ATT_TOPIC_LABEL = "label";
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
private static final int DEFAULT_VISIBILITY = Ranking.VISIBILITY_NO_OVERRIDE;
@@ -166,6 +170,28 @@
r.importance = safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
r.priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
r.visibility = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
+
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (TAG_CHANNEL.equals(tagName)) {
+ String id = parser.getAttributeValue(null, ATT_ID);
+ CharSequence channelName = parser.getAttributeValue(null, ATT_NAME);
+
+ if (!TextUtils.isEmpty(id)) {
+ final NotificationChannel channel =
+ new NotificationChannel(id, channelName);
+ channel.populateFromXml(parser);
+ r.channels.put(id, channel);
+ }
+ }
+ }
}
}
}
@@ -184,11 +210,18 @@
r = new Record();
r.pkg = pkg;
r.uid = uid;
+ NotificationChannel defaultChannel = createDefaultChannel();
+ r.channels.put(defaultChannel.getId(), defaultChannel);
mRecords.put(key, r);
}
return r;
}
+ private NotificationChannel createDefaultChannel() {
+ return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID,
+ mContext.getString(R.string.default_notification_channel_label));
+ }
+
public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
out.startTag(null, TAG_RANKING);
out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
@@ -201,7 +234,8 @@
continue;
}
final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE
- || r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY;
+ || r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY
+ || r.channels.size() > 0;
if (hasNonDefaultSettings) {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
@@ -219,6 +253,10 @@
out.attribute(null, ATT_UID, Integer.toString(r.uid));
}
+ for (NotificationChannel channel : r.channels.values()) {
+ channel.writeXml(out);
+ }
+
out.endTag(null, TAG_PACKAGE);
}
}
@@ -309,11 +347,6 @@
}
}
- private static boolean tryParseBool(String value, boolean defValue) {
- if (TextUtils.isEmpty(value)) return defValue;
- return Boolean.parseBoolean(value);
- }
-
/**
* Gets priority.
*/
@@ -356,6 +389,65 @@
return getOrCreateRecord(packageName, uid).importance;
}
+ @Override
+ public void createNotificationChannel(String pkg, int uid, NotificationChannel channel) {
+ Record r = getOrCreateRecord(pkg, uid);
+ if (r.channels.containsKey(channel.getId()) || channel.getName().equals(
+ mContext.getString(R.string.default_notification_channel_label))) {
+ throw new IllegalArgumentException("Channel already exists");
+ }
+ if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
+ channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
+ }
+ r.channels.put(channel.getId(), channel);
+ updateConfig();
+ }
+
+ @Override
+ public void updateNotificationChannel(int callingUid, String pkg, int uid,
+ NotificationChannel updatedChannel) {
+ Record r = getOrCreateRecord(pkg, uid);
+ NotificationChannel channel = r.channels.get(updatedChannel.getId());
+ if (channel == null) {
+ throw new IllegalArgumentException("Channel does not exist");
+ }
+ if (!isUidSystem(callingUid)) {
+ updatedChannel.setImportance(channel.getImportance());
+ updatedChannel.setName(channel.getName());
+ }
+ if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
+ updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
+ }
+ r.channels.put(updatedChannel.getId(), updatedChannel);
+ updateConfig();
+ }
+
+ @Override
+ public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId) {
+ Record r = getOrCreateRecord(pkg, uid);
+ if (channelId == null) {
+ channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
+ }
+ return r.channels.get(channelId);
+ }
+
+ @Override
+ public void deleteNotificationChannel(String pkg, int uid, String channelId) {
+ Record r = getOrCreateRecord(pkg, uid);
+ r.channels.remove(channelId);
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid) {
+ List<NotificationChannel> channels = new ArrayList<>();
+ Record r = getOrCreateRecord(pkg, uid);
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ channels.add(r.channels.valueAt(i));
+ }
+ return new ParceledListSlice<NotificationChannel>(channels);
+ }
+
/**
* Sets importance.
*/
@@ -420,6 +512,12 @@
pw.print(Notification.visibilityToString(r.visibility));
}
pw.println();
+ for (NotificationChannel channel : r.channels.values()) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(" ");
+ pw.println(channel);
+ }
}
}
}
@@ -449,6 +547,9 @@
if (r.visibility != DEFAULT_VISIBILITY) {
record.put("visibility", Notification.visibilityToString(r.visibility));
}
+ for (NotificationChannel channel : r.channels.values()) {
+ record.put("channel", channel.toJson());
+ }
} catch (JSONException e) {
// pass
}
@@ -530,6 +631,11 @@
}
}
+ private static boolean isUidSystem(int uid) {
+ final int appid = UserHandle.getAppId(uid);
+ return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
+ }
+
private static class Record {
static int UNKNOWN_UID = UserHandle.USER_NULL;
@@ -538,5 +644,7 @@
int importance = DEFAULT_IMPORTANCE;
int priority = DEFAULT_PRIORITY;
int visibility = DEFAULT_VISIBILITY;
+
+ ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
}
}
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
index 32d03ce..50a51b2 100644
--- a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -40,7 +40,9 @@
import com.android.server.notification.NotificationManagerService.DumpFilter;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Calendar;
+import java.util.List;
import java.util.TimeZone;
/**
@@ -91,12 +93,14 @@
pw.print(" mRegistered="); pw.println(mRegistered);
pw.println(" mSubscriptions=");
final long now = System.currentTimeMillis();
- for (Uri conditionId : mSubscriptions.keySet()) {
- pw.print(" ");
- pw.print(meetsSchedule(mSubscriptions.get(conditionId), now) ? "* " : " ");
- pw.println(conditionId);
- pw.print(" ");
- pw.println(mSubscriptions.get(conditionId).toString());
+ synchronized (mSubscriptions) {
+ for (Uri conditionId : mSubscriptions.keySet()) {
+ pw.print(" ");
+ pw.print(meetsSchedule(mSubscriptions.get(conditionId), now) ? "* " : " ");
+ pw.println(conditionId);
+ pw.print(" ");
+ pw.println(mSubscriptions.get(conditionId).toString());
+ }
}
pw.println(" snoozed due to alarm: " + TextUtils.join(SEPARATOR, mSnoozed));
dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, now);
@@ -125,17 +129,21 @@
public void onSubscribe(Uri conditionId) {
if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) {
- notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition");
+ notifyCondition(createCondition(conditionId, Condition.STATE_FALSE, "badCondition"));
return;
}
- mSubscriptions.put(conditionId, toScheduleCalendar(conditionId));
+ synchronized (mSubscriptions) {
+ mSubscriptions.put(conditionId, toScheduleCalendar(conditionId));
+ }
evaluateSubscriptions();
}
@Override
public void onUnsubscribe(Uri conditionId) {
if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
- mSubscriptions.remove(conditionId);
+ synchronized (mSubscriptions) {
+ mSubscriptions.remove(conditionId);
+ }
removeSnoozed(conditionId);
evaluateSubscriptions();
}
@@ -154,36 +162,43 @@
if (mAlarmManager == null) {
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
}
- setRegistered(!mSubscriptions.isEmpty());
final long now = System.currentTimeMillis();
mNextAlarmTime = 0;
long nextUserAlarmTime = getNextAlarm();
- for (Uri conditionId : mSubscriptions.keySet()) {
- final ScheduleCalendar cal = mSubscriptions.get(conditionId);
- if (cal != null && cal.isInSchedule(now)) {
- if (conditionSnoozed(conditionId) || cal.shouldExitForAlarm(now)) {
- notifyCondition(conditionId, Condition.STATE_FALSE, "alarmCanceled");
- addSnoozed(conditionId);
- } else {
- notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule");
- }
- cal.maybeSetNextAlarm(now, nextUserAlarmTime);
- } else {
- notifyCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule");
- removeSnoozed(conditionId);
- if (nextUserAlarmTime == 0) {
+ List<Condition> conditionsToNotify = new ArrayList<>();
+ synchronized (mSubscriptions) {
+ setRegistered(!mSubscriptions.isEmpty());
+ for (Uri conditionId : mSubscriptions.keySet()) {
+ final ScheduleCalendar cal = mSubscriptions.get(conditionId);
+ if (cal != null && cal.isInSchedule(now)) {
+ if (conditionSnoozed(conditionId) || cal.shouldExitForAlarm(now)) {
+ conditionsToNotify.add(createCondition(
+ conditionId, Condition.STATE_FALSE, "alarmCanceled"));
+ addSnoozed(conditionId);
+ } else {
+ conditionsToNotify.add(createCondition(
+ conditionId, Condition.STATE_TRUE, "meetsSchedule"));
+ }
cal.maybeSetNextAlarm(now, nextUserAlarmTime);
+ } else {
+ conditionsToNotify.add(createCondition(
+ conditionId, Condition.STATE_FALSE, "!meetsSchedule"));
+ removeSnoozed(conditionId);
+ if (cal != null && nextUserAlarmTime == 0) {
+ cal.maybeSetNextAlarm(now, nextUserAlarmTime);
+ }
}
- }
- if (cal != null) {
- final long nextChangeTime = cal.getNextChangeTime(now);
- if (nextChangeTime > 0 && nextChangeTime > now) {
- if (mNextAlarmTime == 0 || nextChangeTime < mNextAlarmTime) {
- mNextAlarmTime = nextChangeTime;
+ if (cal != null) {
+ final long nextChangeTime = cal.getNextChangeTime(now);
+ if (nextChangeTime > 0 && nextChangeTime > now) {
+ if (mNextAlarmTime == 0 || nextChangeTime < mNextAlarmTime) {
+ mNextAlarmTime = nextChangeTime;
+ }
}
}
}
}
+ notifyConditions(conditionsToNotify.toArray(new Condition[conditionsToNotify.size()]));
updateAlarm(now, mNextAlarmTime);
}
@@ -240,14 +255,10 @@
}
}
- private void notifyCondition(Uri conditionId, int state, String reason) {
- if (DEBUG) Slog.d(TAG, "notifyCondition " + conditionId
+ private Condition createCondition(Uri id, int state, String reason) {
+ if (DEBUG) Slog.d(TAG, "notifyCondition " + id
+ " " + Condition.stateToString(state)
+ " reason=" + reason);
- notifyCondition(createCondition(conditionId, state));
- }
-
- private Condition createCondition(Uri id, int state) {
final String summary = NOT_SHOWN;
final String line1 = NOT_SHOWN;
final String line2 = NOT_SHOWN;
@@ -315,10 +326,12 @@
public void onReceive(Context context, Intent intent) {
if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
- for (Uri conditionId : mSubscriptions.keySet()) {
- final ScheduleCalendar cal = mSubscriptions.get(conditionId);
- if (cal != null) {
- cal.setTimeZone(Calendar.getInstance().getTimeZone());
+ synchronized (mSubscriptions) {
+ for (Uri conditionId : mSubscriptions.keySet()) {
+ final ScheduleCalendar cal = mSubscriptions.get(conditionId);
+ if (cal != null) {
+ cal.setTimeZone(Calendar.getInstance().getTimeZone());
+ }
}
}
}
diff --git a/services/core/java/com/android/server/notification/VisibilityExtractor.java b/services/core/java/com/android/server/notification/VisibilityExtractor.java
index 2da2b2f..9d0e506 100644
--- a/services/core/java/com/android/server/notification/VisibilityExtractor.java
+++ b/services/core/java/com/android/server/notification/VisibilityExtractor.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
import android.content.Context;
+import android.service.notification.NotificationListenerService;
import android.util.Slog;
/**
@@ -42,8 +43,12 @@
return null;
}
- record.setPackageVisibilityOverride(
- mConfig.getVisibilityOverride(record.sbn.getPackageName(), record.sbn.getUid()));
+ int visibility =
+ mConfig.getVisibilityOverride(record.sbn.getPackageName(), record.sbn.getUid());
+ if (visibility == NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
+ visibility = record.getChannel().getLockscreenVisibility();
+ }
+ record.setPackageVisibilityOverride(visibility);
return null;
}
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index 7badecf..63c0baf 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -102,7 +102,7 @@
@Override
public void onServiceAdded(ComponentName component) {
if (DEBUG) Log.d(TAG, "onServiceAdded " + component);
- mHelper.setConfigAsync(mHelper.getConfig(), "zmc.onServiceAdded");
+ mHelper.setConfig(mHelper.getConfig(), "zmc.onServiceAdded");
}
@Override
@@ -116,7 +116,7 @@
updated |= updateSnoozing(automaticRule);
}
if (updated) {
- mHelper.setConfigAsync(config, "conditionChanged");
+ mHelper.setConfig(config, "conditionChanged");
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 4393761..29fa754 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -35,6 +35,7 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
+import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioSystem;
@@ -490,7 +491,6 @@
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("mZenMode=");
pw.println(Global.zenModeToString(mZenMode));
- dump(pw, prefix, "mDefaultConfig", mDefaultConfig);
final int N = mConfigs.size();
for (int i = 0; i < N; i++) {
dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
@@ -529,7 +529,7 @@
public void readXml(XmlPullParser parser, boolean forRestore)
throws XmlPullParserException, IOException {
- final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
+ final ZenModeConfig config = ZenModeConfig.readXml(parser);
if (config != null) {
if (forRestore) {
//TODO: http://b/22388012
@@ -619,8 +619,10 @@
return setConfigLocked(config, reason, true /*setRingerMode*/);
}
- public void setConfigAsync(ZenModeConfig config, String reason) {
- mHandler.postSetConfig(config, reason);
+ public void setConfig(ZenModeConfig config, String reason) {
+ synchronized (mConfig) {
+ setConfigLocked(config, reason);
+ }
}
private boolean setConfigLocked(ZenModeConfig config, String reason, boolean setRingerMode) {
@@ -736,13 +738,14 @@
// total silence restrictions
final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
- for (int i = USAGE_UNKNOWN; i <= USAGE_VIRTUAL_SOURCE; i++) {
- if (i == USAGE_NOTIFICATION) {
- applyRestrictions(muteNotifications || muteEverything, i);
- } else if (i == USAGE_NOTIFICATION_RINGTONE) {
- applyRestrictions(muteCalls || muteEverything, i);
+ for (int usage : AudioAttributes.SDK_USAGES) {
+ final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
+ if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NOTIFICATION) {
+ applyRestrictions(muteNotifications || muteEverything, usage);
+ } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL) {
+ applyRestrictions(muteCalls || muteEverything, usage);
} else {
- applyRestrictions(muteEverything, i);
+ applyRestrictions(muteEverything, usage);
}
}
}
@@ -806,7 +809,7 @@
try {
parser = resources.getXml(R.xml.default_zen_mode_config);
while (parser.next() != XmlPullParser.END_DOCUMENT) {
- final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
+ final ZenModeConfig config = ZenModeConfig.readXml(parser);
if (config != null) return config;
}
} catch (Exception e) {
@@ -878,45 +881,6 @@
}
}
- private final ZenModeConfig.Migration mConfigMigration = new ZenModeConfig.Migration() {
- @Override
- public ZenModeConfig migrate(ZenModeConfig.XmlV1 v1) {
- if (v1 == null) return null;
- final ZenModeConfig rt = new ZenModeConfig();
- rt.allowCalls = v1.allowCalls;
- rt.allowEvents = v1.allowEvents;
- rt.allowCallsFrom = v1.allowFrom;
- rt.allowMessages = v1.allowMessages;
- rt.allowMessagesFrom = v1.allowFrom;
- rt.allowReminders = v1.allowReminders;
- // don't migrate current exit condition
- final int[] days = ZenModeConfig.XmlV1.tryParseDays(v1.sleepMode);
- if (days != null && days.length > 0) {
- Log.i(TAG, "Migrating existing V1 downtime to single schedule");
- final ScheduleInfo schedule = new ScheduleInfo();
- schedule.days = days;
- schedule.startHour = v1.sleepStartHour;
- schedule.startMinute = v1.sleepStartMinute;
- schedule.endHour = v1.sleepEndHour;
- schedule.endMinute = v1.sleepEndMinute;
- final ZenRule rule = new ZenRule();
- rule.enabled = true;
- rule.name = mContext.getResources()
- .getString(R.string.zen_mode_downtime_feature_name);
- rule.conditionId = ZenModeConfig.toScheduleConditionId(schedule);
- rule.zenMode = v1.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
- : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- rule.component = ScheduleConditionProvider.COMPONENT;
- rt.automaticRules.put(ZenModeConfig.newRuleId(), rule);
- } else {
- Log.i(TAG, "No existing V1 downtime found, generating default schedules");
- appendDefaultScheduleRules(rt);
- }
- appendDefaultEventRules(rt);
- return rt;
- }
- };
-
private final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate {
@Override
public String toString() {
@@ -1082,7 +1046,6 @@
private final class H extends Handler {
private static final int MSG_DISPATCH = 1;
private static final int MSG_METRICS = 2;
- private static final int MSG_SET_CONFIG = 3;
private static final int MSG_APPLY_CONFIG = 4;
private final class ConfigMessageData {
@@ -1090,12 +1053,6 @@
public final String reason;
public final boolean setRingerMode;
- ConfigMessageData(ZenModeConfig config, String reason) {
- this.config = config;
- this.reason = reason;
- this.setRingerMode = false;
- }
-
ConfigMessageData(ZenModeConfig config, String reason, boolean setRingerMode) {
this.config = config;
this.reason = reason;
@@ -1119,10 +1076,6 @@
sendEmptyMessageDelayed(MSG_METRICS, METRICS_PERIOD_MS);
}
- private void postSetConfig(ZenModeConfig config, String reason) {
- sendMessage(obtainMessage(MSG_SET_CONFIG, new ConfigMessageData(config, reason)));
- }
-
private void postApplyConfig(ZenModeConfig config, String reason, boolean setRingerMode) {
sendMessage(obtainMessage(MSG_APPLY_CONFIG,
new ConfigMessageData(config, reason, setRingerMode)));
@@ -1137,12 +1090,6 @@
case MSG_METRICS:
mMetrics.emit();
break;
- case MSG_SET_CONFIG:
- ConfigMessageData configData = (ConfigMessageData) msg.obj;
- synchronized (mConfig) {
- setConfigLocked(configData.config, configData.reason);
- }
- break;
case MSG_APPLY_CONFIG:
ConfigMessageData applyConfigData = (ConfigMessageData) msg.obj;
applyConfig(applyConfigData.config, applyConfigData.reason,
diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
index 8d926f5..68b465a 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
@@ -60,7 +60,7 @@
public EphemeralResolverConnection(Context context, ComponentName componentName) {
mContext = context;
- mIntent = new Intent().setComponent(componentName);
+ mIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE).setComponent(componentName);
}
public final List<EphemeralResolveInfo> getEphemeralResolveInfoList(
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 72c549f..2e18b1c 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -230,6 +230,11 @@
mInstaller.execute("move_ab", apkPath, instructionSet, outputPath);
}
+ public void deleteOdex(String apkPath, String instructionSet, String outputPath)
+ throws InstallerException {
+ mInstaller.execute("delete_odex", apkPath, instructionSet, outputPath);
+ }
+
private static void assertValidInstructionSet(String instructionSet)
throws InstallerException {
for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 37c54cf..48e000d8 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -631,17 +631,20 @@
public void onPackageAdded(String packageName, int uid) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
- for (int i = 0; i < n; i++) {
- IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
- BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(user, cookie.user, "onPackageAdded")) continue;
- try {
- listener.onPackageAdded(user, packageName);
- } catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ try {
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, cookie.user, "onPackageAdded")) continue;
+ try {
+ listener.onPackageAdded(user, packageName);
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
}
+ } finally {
+ mListeners.finishBroadcast();
}
- mListeners.finishBroadcast();
super.onPackageAdded(packageName, uid);
}
@@ -650,17 +653,20 @@
public void onPackageRemoved(String packageName, int uid) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
- for (int i = 0; i < n; i++) {
- IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
- BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(user, cookie.user, "onPackageRemoved")) continue;
- try {
- listener.onPackageRemoved(user, packageName);
- } catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ try {
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, cookie.user, "onPackageRemoved")) continue;
+ try {
+ listener.onPackageRemoved(user, packageName);
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
}
+ } finally {
+ mListeners.finishBroadcast();
}
- mListeners.finishBroadcast();
super.onPackageRemoved(packageName, uid);
}
@@ -669,17 +675,20 @@
public void onPackageModified(String packageName) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
- for (int i = 0; i < n; i++) {
- IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
- BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(user, cookie.user, "onPackageModified")) continue;
- try {
- listener.onPackageChanged(user, packageName);
- } catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ try {
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, cookie.user, "onPackageModified")) continue;
+ try {
+ listener.onPackageChanged(user, packageName);
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
}
+ } finally {
+ mListeners.finishBroadcast();
}
- mListeners.finishBroadcast();
super.onPackageModified(packageName);
}
@@ -688,17 +697,20 @@
public void onPackagesAvailable(String[] packages) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
- for (int i = 0; i < n; i++) {
- IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
- BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(user, cookie.user, "onPackagesAvailable")) continue;
- try {
- listener.onPackagesAvailable(user, packages, isReplacing());
- } catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ try {
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, cookie.user, "onPackagesAvailable")) continue;
+ try {
+ listener.onPackagesAvailable(user, packages, isReplacing());
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
}
+ } finally {
+ mListeners.finishBroadcast();
}
- mListeners.finishBroadcast();
super.onPackagesAvailable(packages);
}
@@ -707,17 +719,20 @@
public void onPackagesUnavailable(String[] packages) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
- for (int i = 0; i < n; i++) {
- IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
- BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(user, cookie.user, "onPackagesUnavailable")) continue;
- try {
- listener.onPackagesUnavailable(user, packages, isReplacing());
- } catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ try {
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, cookie.user, "onPackagesUnavailable")) continue;
+ try {
+ listener.onPackagesUnavailable(user, packages, isReplacing());
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
}
+ } finally {
+ mListeners.finishBroadcast();
}
- mListeners.finishBroadcast();
super.onPackagesUnavailable(packages);
}
@@ -726,17 +741,20 @@
public void onPackagesSuspended(String[] packages) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
- for (int i = 0; i < n; i++) {
- IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
- BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(user, cookie.user, "onPackagesSuspended")) continue;
- try {
- listener.onPackagesSuspended(user, packages);
- } catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ try {
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, cookie.user, "onPackagesSuspended")) continue;
+ try {
+ listener.onPackagesSuspended(user, packages);
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
}
+ } finally {
+ mListeners.finishBroadcast();
}
- mListeners.finishBroadcast();
super.onPackagesSuspended(packages);
}
@@ -745,17 +763,20 @@
public void onPackagesUnsuspended(String[] packages) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
- for (int i = 0; i < n; i++) {
- IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
- BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(user, cookie.user, "onPackagesUnsuspended")) continue;
- try {
- listener.onPackagesUnsuspended(user, packages);
- } catch (RemoteException re) {
- Slog.d(TAG, "Callback failed ", re);
+ try {
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, cookie.user, "onPackagesUnsuspended")) continue;
+ try {
+ listener.onPackagesUnsuspended(user, packages);
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
}
+ } finally {
+ mListeners.finishBroadcast();
}
- mListeners.finishBroadcast();
super.onPackagesUnsuspended(packages);
}
@@ -768,10 +789,10 @@
private void onShortcutChangedInner(@NonNull String packageName,
@UserIdInt int userId) {
+ final int n = mListeners.beginBroadcast();
try {
final UserHandle user = UserHandle.of(userId);
- final int n = mListeners.beginBroadcast();
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
@@ -803,10 +824,11 @@
Slog.d(TAG, "Callback failed ", re);
}
}
- mListeners.finishBroadcast();
} catch (RuntimeException e) {
// When the user is locked we get IllegalState, so just catch all.
Log.w(TAG, e.getMessage(), e);
+ } finally {
+ mListeners.finishBroadcast();
}
}
}
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index bff6d2d..42079fb 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -31,7 +31,7 @@
import android.os.storage.StorageManager;
import android.util.Log;
import android.util.Slog;
-
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.InstallerConnection;
import com.android.internal.os.InstallerConnection.InstallerException;
@@ -40,6 +40,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.TimeUnit;
/**
* A service for A/B OTA dexopting.
@@ -53,6 +54,10 @@
// The synthetic library dependencies denoting "no checks."
private final static String[] NO_LIBRARIES = new String[] { "&" };
+ // The amount of "available" (free - low threshold) space necessary at the start of an OTA to
+ // not bulk-delete unused apps' odex files.
+ private final static long BULK_DELETE_THRESHOLD = 1024 * 1024 * 1024; // 1GB.
+
private final Context mContext;
private final PackageManagerService mPackageManagerService;
@@ -65,6 +70,25 @@
private int completeSize;
+ // MetricsLogger properties.
+
+ // Space before and after.
+ private long availableSpaceBefore;
+ private long availableSpaceAfterBulkDelete;
+ private long availableSpaceAfterDexopt;
+
+ // Packages.
+ private int importantPackageCount;
+ private int otherPackageCount;
+
+ // Number of dexopt commands. This may be different from the count of packages.
+ private int dexoptCommandCountTotal;
+ private int dexoptCommandCountExecuted;
+
+ // For spent time.
+ private long otaDexoptTimeStart;
+
+
public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
this.mContext = context;
this.mPackageManagerService = packageManagerService;
@@ -128,6 +152,18 @@
generatePackageDexopts(p, PackageManagerService.REASON_FIRST_BOOT));
}
completeSize = mDexoptCommands.size();
+
+ long spaceAvailable = getAvailableSpace();
+ if (spaceAvailable < BULK_DELETE_THRESHOLD) {
+ Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
+ + PackageManagerServiceUtils.packagesToString(others));
+ for (PackageParser.Package pkg : others) {
+ deleteOatArtifactsOfPackage(pkg);
+ }
+ }
+ long spaceAvailableNow = getAvailableSpace();
+
+ prepareMetricsLogging(important.size(), others.size(), spaceAvailable, spaceAvailableNow);
}
@Override
@@ -136,6 +172,8 @@
Log.i(TAG, "Cleaning up OTA Dexopt state.");
}
mDexoptCommands = null;
+
+ performMetricsLogging();
}
@Override
@@ -169,28 +207,67 @@
String next = mDexoptCommands.remove(0);
- if (IsFreeSpaceAvailable()) {
+ if (getAvailableSpace() > 0) {
+ dexoptCommandCountExecuted++;
+
return next;
} else {
+ if (DEBUG_DEXOPT) {
+ Log.w(TAG, "Not enough space for OTA dexopt, stopping with "
+ + (mDexoptCommands.size() + 1) + " commands left.");
+ }
mDexoptCommands.clear();
return "(no free space)";
}
}
- /**
- * Check for low space. Returns true if there's space left.
- */
- private boolean IsFreeSpaceAvailable() {
- // TODO: If apps are not installed in the internal /data partition, we should compare
- // against that storage's free capacity.
+ private long getMainLowSpaceThreshold() {
File dataDir = Environment.getDataDirectory();
@SuppressWarnings("deprecation")
long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
if (lowThreshold == 0) {
throw new IllegalStateException("Invalid low memory threshold");
}
+ return lowThreshold;
+ }
+
+ /**
+ * Returns the difference of free space to the low-storage-space threshold. Positive values
+ * indicate free bytes.
+ */
+ private long getAvailableSpace() {
+ // TODO: If apps are not installed in the internal /data partition, we should compare
+ // against that storage's free capacity.
+ long lowThreshold = getMainLowSpaceThreshold();
+
+ File dataDir = Environment.getDataDirectory();
long usableSpace = dataDir.getUsableSpace();
- return (usableSpace >= lowThreshold);
+
+ return usableSpace - lowThreshold;
+ }
+
+ private static String getOatDir(PackageParser.Package pkg) {
+ if (!pkg.canHaveOatDir()) {
+ return null;
+ }
+ File codePath = new File(pkg.codePath);
+ if (codePath.isDirectory()) {
+ return PackageDexOptimizer.getOatDir(codePath).getAbsolutePath();
+ }
+ return null;
+ }
+
+ private void deleteOatArtifactsOfPackage(PackageParser.Package pkg) {
+ String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
+ for (String codePath : pkg.getAllCodePaths()) {
+ for (String isa : instructionSets) {
+ try {
+ mPackageManagerService.mInstaller.deleteOdex(codePath, isa, getOatDir(pkg));
+ } catch (InstallerException e) {
+ Log.e(TAG, "Failed deleting oat files for " + codePath, e);
+ }
+ }
+ }
}
/**
@@ -271,6 +348,55 @@
}
}
+ /**
+ * Initialize logging fields.
+ */
+ private void prepareMetricsLogging(int important, int others, long spaceBegin, long spaceBulk) {
+ availableSpaceBefore = spaceBegin;
+ availableSpaceAfterBulkDelete = spaceBulk;
+ availableSpaceAfterDexopt = 0;
+
+ importantPackageCount = important;
+ otherPackageCount = others;
+
+ dexoptCommandCountTotal = mDexoptCommands.size();
+ dexoptCommandCountExecuted = 0;
+
+ otaDexoptTimeStart = System.nanoTime();
+ }
+
+ private static int inMegabytes(long value) {
+ long in_mega_bytes = value / (1024 * 1024);
+ if (in_mega_bytes > Integer.MAX_VALUE) {
+ Log.w(TAG, "Recording " + in_mega_bytes + "MB of free space, overflowing range");
+ return Integer.MAX_VALUE;
+ }
+ return (int)in_mega_bytes;
+ }
+
+ private void performMetricsLogging() {
+ long finalTime = System.nanoTime();
+
+ MetricsLogger.histogram(mContext, "ota_dexopt_available_space_before_mb",
+ inMegabytes(availableSpaceBefore));
+ MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_bulk_delete_mb",
+ inMegabytes(availableSpaceAfterBulkDelete));
+ MetricsLogger.histogram(mContext, "ota_dexopt_available_space_after_dexopt_mb",
+ inMegabytes(availableSpaceAfterDexopt));
+
+ MetricsLogger.histogram(mContext, "ota_dexopt_num_important_packages",
+ importantPackageCount);
+ MetricsLogger.histogram(mContext, "ota_dexopt_num_other_packages", otherPackageCount);
+
+ MetricsLogger.histogram(mContext, "ota_dexopt_num_commands", dexoptCommandCountTotal);
+ MetricsLogger.histogram(mContext, "ota_dexopt_num_commands_executed",
+ dexoptCommandCountExecuted);
+
+ final int elapsedTimeSeconds =
+ (int) TimeUnit.NANOSECONDS.toSeconds(finalTime - otaDexoptTimeStart);
+ MetricsLogger.histogram(mContext, "ota_dexopt_time_s", elapsedTimeSeconds);
+ }
+
private static class OTADexoptPackageDexOptimizer extends
PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d98539e..2642520 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -212,6 +212,7 @@
import android.util.Log;
import android.util.LogPrinter;
import android.util.MathUtils;
+import android.util.Pair;
import android.util.PrintStreamPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -366,8 +367,8 @@
/** REMOVE. According to Svet, this was only used to reset permissions during development. */
static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false;
- // STOPSHIP; b/30256615
- private static final boolean DISABLE_EPHEMERAL_APPS = !Build.IS_DEBUGGABLE;
+ private static final boolean DISABLE_EPHEMERAL_APPS = false;
+ private static final boolean HIDE_EPHEMERAL_APIS = true;
private static final int RADIO_UID = Process.PHONE_UID;
private static final int LOG_UID = Process.LOG_UID;
@@ -3180,7 +3181,7 @@
// reader
synchronized (mPackages) {
for (int i=names.length-1; i>=0; i--) {
- String cur = mSettings.getRenamedPackage(names[i]);
+ String cur = mSettings.getRenamedPackageLPr(names[i]);
out[i] = cur != null ? cur : names[i];
}
}
@@ -6301,7 +6302,7 @@
@Override
public ParceledListSlice<EphemeralApplicationInfo> getEphemeralApplications(int userId) {
- if (isEphemeralDisabled()) {
+ if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
return null;
}
@@ -6325,7 +6326,7 @@
enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"isEphemeral");
- if (isEphemeralDisabled()) {
+ if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
return false;
}
@@ -6343,7 +6344,7 @@
@Override
public byte[] getEphemeralApplicationCookie(String packageName, int userId) {
- if (isEphemeralDisabled()) {
+ if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
return null;
}
@@ -6361,7 +6362,7 @@
@Override
public boolean setEphemeralApplicationCookie(String packageName, byte[] cookie, int userId) {
- if (isEphemeralDisabled()) {
+ if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
return true;
}
@@ -6379,7 +6380,7 @@
@Override
public Bitmap getEphemeralApplicationIcon(String packageName, int userId) {
- if (isEphemeralDisabled()) {
+ if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
return null;
}
@@ -6845,7 +6846,7 @@
// reader
synchronized (mPackages) {
// Look to see if we already know about this package.
- String oldName = mSettings.getRenamedPackage(pkg.packageName);
+ String oldName = mSettings.getRenamedPackageLPr(pkg.packageName);
if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
// This package has been renamed to its original name. Let's
// use that.
@@ -8174,7 +8175,7 @@
if (pkg.mOriginalPackages != null) {
// This package may need to be renamed to a previously
// installed name. Let's check on that...
- final String renamed = mSettings.getRenamedPackage(pkg.mRealPackage);
+ final String renamed = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
if (pkg.mOriginalPackages.contains(renamed)) {
// This package had originally been installed as the
// original name, and we have already taken care of
@@ -8232,18 +8233,42 @@
}
}
- // Just create the setting, don't add it yet. For already existing packages
- // the PkgSetting exists already and doesn't have to be created.
- pkgSetting = mSettings.getPackageWithBenefitsLPw(pkg, origPackage, realName, suid,
- destCodeFile, destResourceFile, pkg.applicationInfo.nativeLibraryRootDir,
- pkg.applicationInfo.primaryCpuAbi,
- pkg.applicationInfo.secondaryCpuAbi,
- pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags,
- user);
- if (pkgSetting == null) {
- throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
- "Creating application package " + pkg.packageName + " failed");
+ pkgSetting = mSettings.getPackageLPr(pkg.packageName);
+ if (pkgSetting != null && pkgSetting.sharedUser != suid) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Package " + pkg.packageName + " shared user changed from "
+ + (pkgSetting.sharedUser != null ? pkgSetting.sharedUser.name : "<nothing>")
+ + " to "
+ + (suid != null ? suid.name : "<nothing>")
+ + "; replacing with new");
+ pkgSetting = null;
}
+ final PackageSetting oldPkgSetting =
+ pkgSetting == null ? null : new PackageSetting(pkgSetting);
+ final PackageSetting disabledPkgSetting =
+ mSettings.getDisabledSystemPkgLPr(pkg.packageName);
+ if (pkgSetting == null) {
+ final String parentPackageName = (pkg.parentPackage != null)
+ ? pkg.parentPackage.packageName : null;
+ pkgSetting = Settings.createNewSetting(pkg.packageName, origPackage,
+ disabledPkgSetting, realName, suid, destCodeFile, destResourceFile,
+ pkg.applicationInfo.nativeLibraryRootDir, pkg.applicationInfo.primaryCpuAbi,
+ pkg.applicationInfo.secondaryCpuAbi, pkg.mVersionCode,
+ pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags, user,
+ true /*allowInstall*/, parentPackageName, pkg.getChildPackageNames(),
+ UserManagerService.getInstance());
+ if (origPackage != null) {
+ mSettings.addRenamedPackageLPw(pkg.packageName, origPackage.name);
+ }
+ mSettings.addUserToSettingLPw(pkgSetting);
+ } else {
+ Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, suid, destCodeFile,
+ pkg.applicationInfo.nativeLibraryDir, pkg.applicationInfo.primaryCpuAbi,
+ pkg.applicationInfo.secondaryCpuAbi, pkg.applicationInfo.flags,
+ pkg.applicationInfo.privateFlags, pkg.getChildPackageNames(),
+ UserManagerService.getInstance());
+ }
+ mSettings.writeUserRestrictions(pkgSetting, oldPkgSetting);
if (pkgSetting.origPackage != null) {
// If we are first transitioning from an original package,
@@ -11309,6 +11334,19 @@
private static final class EphemeralIntentResolver
extends IntentResolver<EphemeralResolveIntentInfo, EphemeralResolveInfo> {
+ /**
+ * The result that has the highest defined order. Ordering applies on a
+ * per-package basis. Mapping is from package name to Pair of order and
+ * EphemeralResolveInfo.
+ * <p>
+ * NOTE: This is implemented as a field variable for convenience and efficiency.
+ * By having a field variable, we're able to track filter ordering as soon as
+ * a non-zero order is defined. Otherwise, multiple loops across the result set
+ * would be needed to apply ordering. If the intent resolver becomes re-entrant,
+ * this needs to be contained entirely within {@link #filterResults()}.
+ */
+ final ArrayMap<String, Pair<Integer, EphemeralResolveInfo>> mOrderResult = new ArrayMap<>();
+
@Override
protected EphemeralResolveIntentInfo[] newArray(int size) {
return new EphemeralResolveIntentInfo[size];
@@ -11325,7 +11363,51 @@
if (!sUserManager.exists(userId)) {
return null;
}
- return info.getEphemeralResolveInfo();
+ final String packageName = info.getEphemeralResolveInfo().getPackageName();
+ final Integer order = info.getOrder();
+ final Pair<Integer, EphemeralResolveInfo> lastOrderResult =
+ mOrderResult.get(packageName);
+ // ordering is enabled and this item's order isn't high enough
+ if (lastOrderResult != null && lastOrderResult.first >= order) {
+ return null;
+ }
+ final EphemeralResolveInfo res = info.getEphemeralResolveInfo();
+ if (order > 0) {
+ // non-zero order, enable ordering
+ mOrderResult.put(packageName, new Pair<>(order, res));
+ }
+ return res;
+ }
+
+ @Override
+ protected void filterResults(List<EphemeralResolveInfo> results) {
+ // only do work if ordering is enabled [most of the time it won't be]
+ if (mOrderResult.size() == 0) {
+ return;
+ }
+ int resultSize = results.size();
+ for (int i = 0; i < resultSize; i++) {
+ final EphemeralResolveInfo info = results.get(i);
+ final String packageName = info.getPackageName();
+ final Pair<Integer, EphemeralResolveInfo> savedInfo = mOrderResult.get(packageName);
+ if (savedInfo == null) {
+ // package doesn't having ordering
+ continue;
+ }
+ if (savedInfo.second == info) {
+ // circled back to the highest ordered item; remove from order list
+ mOrderResult.remove(savedInfo);
+ if (mOrderResult.size() == 0) {
+ // no more ordered items
+ break;
+ }
+ continue;
+ }
+ // item has a worse order, remove it from the result list
+ results.remove(i);
+ resultSize--;
+ i--;
+ }
}
}
@@ -14127,7 +14209,7 @@
if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
synchronized(mPackages) {
- final String renamedPackage = mSettings.getRenamedPackage(pkgName);
+ final String renamedPackage = mSettings.getRenamedPackageLPr(pkgName);
if (renamedPackage != null) {
// A package with the same name is already installed, though
// it has been renamed to an older name. The package we
@@ -14994,7 +15076,7 @@
synchronized (mPackages) {
// Check if installing already existing package
if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
- String oldName = mSettings.getRenamedPackage(pkgName);
+ String oldName = mSettings.getRenamedPackageLPr(pkgName);
if (pkg.mOriginalPackages != null
&& pkg.mOriginalPackages.contains(oldName)
&& mPackages.containsKey(oldName)) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 751c585..cfd0af7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -19,6 +19,7 @@
import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
import static com.android.server.pm.PackageManagerService.TAG;
+import android.annotation.NonNull;
import android.app.AppGlobals;
import android.content.Intent;
import android.content.pm.PackageParser;
@@ -35,13 +36,9 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
+import java.util.function.Predicate;
/**
* Class containing helper methods for the PackageManagerService.
@@ -67,34 +64,51 @@
return pkgNames;
}
- private static void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs,
- long estimatedPreviousSystemUseTime,
- long dexOptLRUThresholdInMills) {
- // Filter out packages that aren't recently used.
- int total = pkgs.size();
- int skipped = 0;
- for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) {
- PackageParser.Package pkg = i.next();
- long then = pkg.getLatestForegroundPackageUseTimeInMills();
- if (then < estimatedPreviousSystemUseTime - dexOptLRUThresholdInMills) {
- if (DEBUG_DEXOPT) {
- Log.i(TAG, "Skipping dexopt of " + pkg.packageName +
- " last used in foreground: " +
- ((then == 0) ? "never" : new Date(then)));
- }
- i.remove();
- skipped++;
- } else {
- if (DEBUG_DEXOPT) {
- Log.i(TAG, "Will dexopt " + pkg.packageName +
- " last used in foreground: " +
- ((then == 0) ? "never" : new Date(then)));
- }
+ // Sort a list of apps by their last usage, most recently used apps first. The order of
+ // packages without usage data is undefined (but they will be sorted after the packages
+ // that do have usage data).
+ public static void sortPackagesByUsageDate(List<PackageParser.Package> pkgs,
+ PackageManagerService packageManagerService) {
+ if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
+ return;
+ }
+
+ Collections.sort(pkgs, (pkg1, pkg2) ->
+ Long.compare(pkg2.getLatestForegroundPackageUseTimeInMills(),
+ pkg1.getLatestForegroundPackageUseTimeInMills()));
+ }
+
+ // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
+ // package will be removed from {@code packages} and added to {@code result} with its
+ // dependencies. If usage data is available, the positive packages will be sorted by usage
+ // data (with {@code sortTemp} as temporary storage).
+ private static void applyPackageFilter(Predicate<PackageParser.Package> filter,
+ Collection<PackageParser.Package> result,
+ Collection<PackageParser.Package> packages,
+ @NonNull List<PackageParser.Package> sortTemp,
+ PackageManagerService packageManagerService) {
+ for (PackageParser.Package pkg : packages) {
+ if (filter.test(pkg)) {
+ sortTemp.add(pkg);
}
}
- if (DEBUG_DEXOPT) {
- Log.i(TAG, "Skipped dexopt " + skipped + " of " + total);
+
+ sortPackagesByUsageDate(sortTemp, packageManagerService);
+ packages.removeAll(sortTemp);
+
+ for (PackageParser.Package pkg : sortTemp) {
+ result.add(pkg);
+
+ Collection<PackageParser.Package> deps =
+ packageManagerService.findSharedNonSystemLibraries(pkg);
+ if (!deps.isEmpty()) {
+ deps.removeAll(result);
+ result.addAll(deps);
+ packages.removeAll(deps);
+ }
}
+
+ sortTemp.clear();
}
// Sort apps by importance for dexopt ordering. Important apps are given
@@ -104,46 +118,25 @@
PackageManagerService packageManagerService) {
ArrayList<PackageParser.Package> remainingPkgs = new ArrayList<>(packages);
LinkedList<PackageParser.Package> result = new LinkedList<>();
+ ArrayList<PackageParser.Package> sortTemp = new ArrayList<>(remainingPkgs.size());
// Give priority to core apps.
- for (PackageParser.Package pkg : remainingPkgs) {
- if (pkg.coreApp) {
- if (DEBUG_DEXOPT) {
- Log.i(TAG, "Adding core app " + result.size() + ": " + pkg.packageName);
- }
- result.add(pkg);
- }
- }
- remainingPkgs.removeAll(result);
+ applyPackageFilter((pkg) -> pkg.coreApp, result, remainingPkgs, sortTemp,
+ packageManagerService);
// Give priority to system apps that listen for pre boot complete.
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
- ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
- for (PackageParser.Package pkg : remainingPkgs) {
- if (pkgNames.contains(pkg.packageName)) {
- if (DEBUG_DEXOPT) {
- Log.i(TAG, "Adding pre boot system app " + result.size() + ": " +
- pkg.packageName);
- }
- result.add(pkg);
- }
- }
- remainingPkgs.removeAll(result);
+ final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
+ applyPackageFilter((pkg) -> pkgNames.contains(pkg.packageName), result, remainingPkgs,
+ sortTemp, packageManagerService);
// Give priority to apps used by other apps.
- for (PackageParser.Package pkg : remainingPkgs) {
- if (PackageDexOptimizer.isUsedByOtherApps(pkg)) {
- if (DEBUG_DEXOPT) {
- Log.i(TAG, "Adding app used by other apps " + result.size() + ": " +
- pkg.packageName);
- }
- result.add(pkg);
- }
- }
- remainingPkgs.removeAll(result);
+ applyPackageFilter((pkg) -> PackageDexOptimizer.isUsedByOtherApps(pkg), result,
+ remainingPkgs, sortTemp, packageManagerService);
// Filter out packages that aren't recently used, add all remaining apps.
// TODO: add a property to control this?
+ Predicate<PackageParser.Package> remainingPredicate;
if (!remainingPkgs.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Looking at historical package use");
@@ -159,34 +152,24 @@
lastUsed.getLatestForegroundPackageUseTimeInMills();
// Be defensive if for some reason package usage has bogus data.
if (estimatedPreviousSystemUseTime != 0) {
- filterRecentlyUsedApps(remainingPkgs, estimatedPreviousSystemUseTime,
- SEVEN_DAYS_IN_MILLISECONDS);
+ final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
+ remainingPredicate =
+ (pkg) -> pkg.getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
+ } else {
+ // No meaningful historical info. Take all.
+ remainingPredicate = (pkg) -> true;
}
+ sortPackagesByUsageDate(remainingPkgs, packageManagerService);
+ } else {
+ // No historical info. Take all.
+ remainingPredicate = (pkg) -> true;
}
- result.addAll(remainingPkgs);
-
- // Now go ahead and also add the libraries required for these packages.
- // TODO: Think about interleaving things.
- Set<PackageParser.Package> dependencies = new HashSet<>();
- for (PackageParser.Package p : result) {
- dependencies.addAll(packageManagerService.findSharedNonSystemLibraries(p));
- }
- if (!dependencies.isEmpty()) {
- // We might have packages already in `result` that are dependencies
- // of other packages. Make sure we don't add those to the list twice.
- dependencies.removeAll(result);
- }
- result.addAll(dependencies);
+ applyPackageFilter(remainingPredicate, result, remainingPkgs, sortTemp,
+ packageManagerService);
if (DEBUG_DEXOPT) {
- StringBuilder sb = new StringBuilder();
- for (PackageParser.Package pkg : result) {
- if (sb.length() > 0) {
- sb.append(", ");
- }
- sb.append(pkg.packageName);
- }
- Log.i(TAG, "Packages to be dexopted: " + sb.toString());
+ Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
+ Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgs));
}
return result;
@@ -203,4 +186,15 @@
throw ee.rethrowAsIOException();
}
}
+
+ public static String packagesToString(Collection<PackageParser.Package> c) {
+ StringBuilder sb = new StringBuilder();
+ for (PackageParser.Package pkg : c) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(pkg.packageName);
+ }
+ return sb.toString();
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index a4604a6..80a398a 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -29,10 +29,14 @@
final class PackageSetting extends PackageSettingBase {
int appId;
PackageParser.Package pkg;
+ /**
+ * WARNING. The object reference is important. We perform integer equality and NOT
+ * object equality to check whether shared user settings are the same.
+ */
SharedUserSetting sharedUser;
/**
* Temporary holding space for the shared user ID. While parsing package settings, the
- * shared users tag may be after the packages. In this case, we must delay linking the
+ * shared users tag may come after the packages. In this case, we must delay linking the
* shared user setting with the package setting. The shared user ID lets us link the
* two objects.
*/
@@ -54,7 +58,17 @@
* Note that it keeps the same PackageParser.Package instance.
*/
PackageSetting(PackageSetting orig) {
- super(orig);
+ super(orig, orig.realName);
+ doCopy(orig);
+ }
+
+ /**
+ * New instance of PackageSetting replicating the original settings, but, allows specifying
+ * a real package name.
+ * Note that it keeps the same PackageParser.Package instance.
+ */
+ PackageSetting(PackageSetting orig, String realPkgName) {
+ super(orig, realPkgName);
doCopy(orig);
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index c08bc57..3ad2ae1 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -27,6 +27,8 @@
import android.util.ArraySet;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@@ -99,7 +101,7 @@
boolean uidError;
- PackageSignatures signatures = new PackageSignatures();
+ PackageSignatures signatures;
boolean installPermissionsFixed;
@@ -147,11 +149,17 @@
secondaryCpuAbiString, cpuAbiOverrideString, pVersionCode);
}
- /** New instance of PackageSetting with one-level-deep cloning. */
- PackageSettingBase(PackageSettingBase base) {
+ /**
+ * New instance of PackageSetting with one-level-deep cloning.
+ * <p>
+ * IMPORTANT: With a shallow copy, we do NOT create new contained objects.
+ * This means, for example, changes to the user state of the original PackageSetting
+ * will also change the user state in its copy.
+ */
+ PackageSettingBase(PackageSettingBase base, String realName) {
super(base);
name = base.name;
- realName = base.realName;
+ this.realName = realName;
doCopy(base);
}
@@ -167,6 +175,7 @@
this.secondaryCpuAbiString = secondaryCpuAbiString;
this.cpuAbiOverrideString = cpuAbiOverrideString;
this.versionCode = pVersionCode;
+ this.signatures = new PackageSignatures();
}
public void setInstallerPackageName(String packageName) {
@@ -280,6 +289,12 @@
return readUserState(userId).installed;
}
+ /** Only use for testing. Do NOT use in production code. */
+ @VisibleForTesting
+ SparseArray<PackageUserState> getUserState() {
+ return userState;
+ }
+
boolean isAnyInstalled(int[] users) {
for (int user: users) {
if (readUserState(user).installed) {
diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java
index 8f9968ec..8a427cd 100644
--- a/services/core/java/com/android/server/pm/PermissionsState.java
+++ b/services/core/java/com/android/server/pm/PermissionsState.java
@@ -140,6 +140,36 @@
}
}
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final PermissionsState other = (PermissionsState) obj;
+
+ if (mPermissions == null) {
+ if (other.mPermissions != null) {
+ return false;
+ }
+ } else if (!mPermissions.equals(other.mPermissions)) {
+ return false;
+ }
+ if (mPermissionReviewRequired == null) {
+ if (other.mPermissionReviewRequired != null) {
+ return false;
+ }
+ } else if (!mPermissionReviewRequired.equals(other.mPermissionReviewRequired)) {
+ return false;
+ }
+ return Arrays.equals(mGlobalGids, other.mGlobalGids);
+ }
+
public boolean isPermissionReviewRequired(int userId) {
return mPermissionReviewRequired != null && mPermissionReviewRequired.get(userId);
}
@@ -566,6 +596,7 @@
return PERMISSION_OPERATION_SUCCESS;
}
+ // TODO: fix this to use arraycopy and append all ints in one go
private static int[] appendInts(int[] current, int[] added) {
if (current != null && added != null) {
for (int guid : added) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 52313b1..27ac3ad 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -24,6 +24,7 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
@@ -428,33 +429,22 @@
mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}
- PackageSetting getPackageLPw(String pkgName) {
+ PackageSetting getPackageLPr(String pkgName) {
return peekPackageLPr(pkgName);
}
- PackageSetting getPackageWithBenefitsLPw(PackageParser.Package pkg, PackageSetting origPackage,
- String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
- String legacyNativeLibraryPathString, String primaryCpuAbi, String secondaryCpuAbi,
- int pkgFlags, int pkgPrivateFlags, UserHandle user)
- throws PackageManagerException {
- final String name = pkg.packageName;
- final String parentPackageName = (pkg.parentPackage != null)
- ? pkg.parentPackage.packageName : null;
- PackageSetting p = getPackageWithBenefitsLPw(name, origPackage, realName, sharedUser,
- codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbi,
- secondaryCpuAbi, pkg.mVersionCode, pkgFlags, pkgPrivateFlags, user,
- true /*allowInstall*/, parentPackageName, pkg.getChildPackageNames());
- return p;
- }
-
PackageSetting peekPackageLPr(String pkgName) {
return mPackages.get(pkgName);
}
- String getRenamedPackage(String pkgName) {
+ String getRenamedPackageLPr(String pkgName) {
return mRenamedPackages.get(pkgName);
}
+ String addRenamedPackageLPw(String pkgName, String origPkgName) {
+ return mRenamedPackages.put(pkgName, origPkgName);
+ }
+
void setInstallStatus(String pkgName, final int status) {
PackageSetting p = mPackages.get(pkgName);
if(p != null) {
@@ -684,280 +674,101 @@
}
}
- private PackageSetting getPackageWithBenefitsLPw(String name, PackageSetting origPackage,
- String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
- String legacyNativeLibraryPathString, String primaryCpuAbiString,
- String secondaryCpuAbiString, int vc, int pkgFlags, int pkgPrivateFlags,
- UserHandle installUser, boolean allowInstall, String parentPackage,
- List<String> childPackageNames) throws PackageManagerException {
- final UserManagerService userManager = UserManagerService.getInstance();
- final PackageSetting disabledPackage = getDisabledSystemPkgLPr(name);
- final PackageSetting peekPackage = peekPackageLPr(name);
- final PackageSetting oldPackage =
- peekPackage == null ? null : new PackageSetting(peekPackage);
- final PackageSetting p = updatePackageSetting(peekPackage, name, realName,
- origPackage, disabledPackage, sharedUser, codePath, resourcePath,
- legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, vc,
- pkgFlags, pkgPrivateFlags, installUser, allowInstall, parentPackage,
- childPackageNames, userManager);
- final boolean newPackageCreated = (peekPackage == null || p != peekPackage);
- final boolean renamedPackage = newPackageCreated && origPackage != null;
- if (renamedPackage) {
- mRenamedPackages.put(name, origPackage.name);
- }
- if (newPackageCreated) {
- if (p.appId == 0) {
- // Assign new user ID
- p.appId = newUserIdLPw(p);
- } else {
- // Add new setting to list of user IDs
- addUserIdLPw(p.appId, p, name);
- }
- if (p.appId < 0) {
- PackageManagerService.reportSettingsProblem(Log.WARN,
- "Package " + name + " could not be assigned a valid uid");
- throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
- "Creating application package " + name + " failed");
- }
- }
- if (peekPackageLPr(name) != null) {
- final List<UserInfo> allUsers = getAllUsers(UserManagerService.getInstance());
- if (allUsers != null) {
- for (UserInfo user : allUsers) {
- final PackageUserState oldUserState = oldPackage == null
- ? PackageSettingBase.DEFAULT_USER_STATE
- : oldPackage.readUserState(user.id);
- if (!oldUserState.equals(p.readUserState(user.id))) {
- writePackageRestrictionsLPr(user.id);
- }
- }
- }
- }
- return p;
- }
-
/**
- * Updates the given package setting using the provided information.
- * <p>
- * WARNING: The provided PackageSetting object may be mutated.
- *
- * @param pkgSetting The package setting to update. If {@code null} a new PackageSetting
- * object is created and populated.
- * @param pkgName The name of the package.
- * @param realPkgName The real name of the package. A package can change its name during
- * an update. In that case, all references are to the package's original name, but, we
- * still need it's real [new] name.
- * @param originalPkg If the package name has changed, the package setting for the originally
- * installed package. May be {@code null}.
- * @param disabledPkg If the package is a system package that has been updated, the package
- * setting for the disabled system package. May be {@code null}.
- * @param sharedUser Shared user settings if the package declares a shared user ID.
- * May be {@code null}.
- * @param codePath The path to the applications code.
- * @param resourcePath The path to the application's resources.
- * @param legacyNativeLibraryPath The path where native libraries are unpacked.
- * @param primaryCpuAbi The primary CPU architecture. May be {@code null}.
- * @param secondaryCpuAbi The secondary CPU architecture, if one is defined.
- * May be {@code null}.
- * @param versionCode The version code of the package.
- * @param pkgFlags Application flags for the package. {@link ApplicationInfo#flags}.
- * @param pkgPrivateFlags Private application flags for the package.
- * {@link ApplicationInfo#privateFlags}.
- * @param installUser The user to install the package for. May be {@code null}.
- * @param allowInstall Whether or not the user state for a newly created package setting can
- * be installed.
- * @param parentPkgName The name of the parent package. May be {@code null}.
- * @param childPkgNames A list of child package names. May be {@code null}.
- * @param userManager The user manager service.
- * @return An updated package setting. It may be different than pkgSetting even when pkgSetting
- * is not {@code null}.
- * @throws PackageManagerException
+ * Creates a new {@code PackageSetting} object.
+ * Use this method instead of the constructor to ensure a settings object is created
+ * with the correct base.
*/
- static PackageSetting updatePackageSetting(@Nullable PackageSetting pkgSetting,
- @NonNull String pkgName, @Nullable String realPkgName,
- @Nullable PackageSetting originalPkg, @Nullable PackageSetting disabledPkg,
- @Nullable SharedUserSetting sharedUser, @NonNull File codePath,
- @NonNull File resourcePath, @Nullable String legacyNativeLibraryPath,
- @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int versionCode,
- int pkgFlags, int pkgPrivateFlags, @Nullable UserHandle installUser,
- boolean allowInstall, @Nullable String parentPkgName,
- @Nullable List<String> childPkgNames, @NonNull UserManagerService userManager)
- throws PackageManagerException {
- if (pkgSetting != null) {
- if (pkgSetting.sharedUser != sharedUser) {
- PackageManagerService.reportSettingsProblem(Log.WARN,
- "Package " + pkgName + " shared user changed from "
- + (pkgSetting.sharedUser != null ? pkgSetting.sharedUser.name : "<nothing>")
- + " to "
- + (sharedUser != null ? sharedUser.name : "<nothing>")
- + "; replacing with new");
- pkgSetting = null;
- } else {
- // If what we are scanning is a system (and possibly privileged) package,
- // then make it so, regardless of whether it was previously installed only
- // in the data partition.
- pkgSetting.pkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM;
- pkgSetting.pkgPrivateFlags |=
- pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
- }
- }
- if (pkgSetting != null) {
+ static @NonNull PackageSetting createNewSetting(String pkgName, PackageSetting originalPkg,
+ PackageSetting disabledPkg, String realPkgName, SharedUserSetting sharedUser,
+ File codePath, File resourcePath, String legacyNativeLibraryPath, String primaryCpuAbi,
+ String secondaryCpuAbi, int versionCode, int pkgFlags, int pkgPrivateFlags,
+ UserHandle installUser, boolean allowInstall, String parentPkgName,
+ List<String> childPkgNames, UserManagerService userManager) {
+ final PackageSetting pkgSetting;
+ if (originalPkg != null) {
+ if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
+ + pkgName + " is adopting original package " + originalPkg.name);
+ pkgSetting = new PackageSetting(originalPkg, pkgName /*realPkgName*/);
+ pkgSetting.childPackageNames =
+ (childPkgNames != null) ? new ArrayList<>(childPkgNames) : null;
+ pkgSetting.codePath = codePath;
+ pkgSetting.legacyNativeLibraryPathString = legacyNativeLibraryPath;
+ pkgSetting.origPackage = originalPkg;
+ pkgSetting.parentPackageName = parentPkgName;
+ pkgSetting.pkgFlags = pkgFlags;
+ pkgSetting.pkgPrivateFlags = pkgPrivateFlags;
pkgSetting.primaryCpuAbiString = primaryCpuAbi;
+ pkgSetting.resourcePath = resourcePath;
pkgSetting.secondaryCpuAbiString = secondaryCpuAbi;
- if (childPkgNames != null) {
- pkgSetting.childPackageNames = new ArrayList<>(childPkgNames);
- }
-
- if (!pkgSetting.codePath.equals(codePath)) {
- // Check to see if its a disabled system app
- if ((pkgSetting.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- // This is an updated system app with versions in both system
- // and data partition. Just let the most recent version
- // take precedence.
- Slog.w(PackageManagerService.TAG,
- "Trying to update system app code path from "
- + pkgSetting.codePathString + " to " + codePath.toString());
- } else {
- // Just a change in the code path is not an issue, but
- // let's log a message about it.
- Slog.i(PackageManagerService.TAG,
- "Package " + pkgName + " codePath changed from "
- + pkgSetting.codePath + " to " + codePath
- + "; Retaining data and using new");
-
- // The owner user's installed flag is set false
- // when the application was installed by other user
- // and the installed flag is not updated
- // when the application is appended as system app later.
- if ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0
- && disabledPkg == null) {
- List<UserInfo> allUserInfos = getAllUsers(userManager);
- if (allUserInfos != null) {
- for (UserInfo userInfo : allUserInfos) {
- pkgSetting.setInstalled(true, userInfo.id);
- }
- }
- }
-
- /*
- * Since we've changed paths, we need to prefer the new
- * native library path over the one stored in the
- * package settings since we might have moved from
- * internal to external storage or vice versa.
- */
- pkgSetting.legacyNativeLibraryPathString = legacyNativeLibraryPath;
- }
- }
- }
- // TODO: split package setting creation logic out; invoke where getPackageLPw() is
- // called from
- if (pkgSetting == null) {
- if (originalPkg != null) {
- // We are consuming the data from an existing package.
- pkgSetting = new PackageSetting(originalPkg.name, pkgName, codePath, resourcePath,
- legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
- null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
- parentPkgName, childPkgNames, 0 /*sharedUserId*/);
- if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
- + pkgName + " is adopting original package " + originalPkg.name);
- // Note that we will retain the new package's signature so
- // that we can keep its data.
- PackageSignatures s = pkgSetting.signatures;
- pkgSetting.copyFrom(originalPkg);
- pkgSetting.signatures = s;
- pkgSetting.sharedUser = originalPkg.sharedUser;
- pkgSetting.appId = originalPkg.appId;
- pkgSetting.origPackage = originalPkg;
- pkgSetting.getPermissionsState().copyFrom(originalPkg.getPermissionsState());
- pkgName = originalPkg.name;
- // Update new package state.
- pkgSetting.setTimeStamp(codePath.lastModified());
- } else {
- pkgSetting = new PackageSetting(pkgName, realPkgName, codePath, resourcePath,
- legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
- null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
- parentPkgName, childPkgNames, 0 /*sharedUserId*/);
- pkgSetting.setTimeStamp(codePath.lastModified());
- pkgSetting.sharedUser = sharedUser;
- // If this is not a system app, it starts out stopped.
- if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
- if (DEBUG_STOPPED) {
- RuntimeException e = new RuntimeException("here");
- e.fillInStackTrace();
- Slog.i(PackageManagerService.TAG, "Stopping package " + pkgName, e);
- }
- List<UserInfo> users = getAllUsers(userManager);
- final int installUserId = installUser != null ? installUser.getIdentifier() : 0;
- if (users != null && allowInstall) {
- for (UserInfo user : users) {
- // By default we consider this app to be installed
- // for the user if no user has been specified (which
- // means to leave it at its original value, and the
- // original default value is true), or we are being
- // asked to install for all users, or this is the
- // user we are installing for.
- final boolean installed = installUser == null
- || (installUserId == UserHandle.USER_ALL
- && !isAdbInstallDisallowed(userManager, user.id))
- || installUserId == user.id;
- pkgSetting.setUserState(user.id, 0, COMPONENT_ENABLED_STATE_DEFAULT,
- installed,
- true, // stopped,
- true, // notLaunched
- false, // hidden
- false, // suspended
- null, null, null,
- false, // blockUninstall
- INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0);
- }
- }
- }
- if (sharedUser != null) {
- pkgSetting.appId = sharedUser.userId;
- } else {
- // Clone the setting here for disabled system packages
- if (disabledPkg != null) {
- // For disabled packages a new setting is created
- // from the existing user id. This still has to be
- // added to list of user id's
- // Copy signatures from previous setting
- if (disabledPkg.signatures.mSignatures != null) {
- pkgSetting.signatures.mSignatures =
- disabledPkg.signatures.mSignatures.clone();
- }
- pkgSetting.appId = disabledPkg.appId;
- // Clone permissions
- pkgSetting.getPermissionsState().copyFrom(
- disabledPkg.getPermissionsState());
- // Clone component info
- List<UserInfo> users = getAllUsers(userManager);
- if (users != null) {
- for (UserInfo user : users) {
- int userId = user.id;
- pkgSetting.setDisabledComponentsCopy(
- disabledPkg.getDisabledComponents(userId), userId);
- pkgSetting.setEnabledComponentsCopy(
- disabledPkg.getEnabledComponents(userId), userId);
- }
- }
- }
- }
- }
+ // NOTE: Create a deeper copy of the package signatures so we don't
+ // overwrite the signatures in the original package setting.
+ pkgSetting.signatures = new PackageSignatures();
+ pkgSetting.versionCode = versionCode;
+ // Update new package state.
+ pkgSetting.setTimeStamp(codePath.lastModified());
} else {
- if (installUser != null && allowInstall) {
- // The caller has explicitly specified the user they want this
- // package installed for, and the package already exists.
- // Make sure it conforms to the new request.
+ pkgSetting = new PackageSetting(pkgName, realPkgName, codePath, resourcePath,
+ legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
+ null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
+ parentPkgName, childPkgNames, 0 /*sharedUserId*/);
+ pkgSetting.setTimeStamp(codePath.lastModified());
+ pkgSetting.sharedUser = sharedUser;
+ // If this is not a system app, it starts out stopped.
+ if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
+ if (DEBUG_STOPPED) {
+ RuntimeException e = new RuntimeException("here");
+ e.fillInStackTrace();
+ Slog.i(PackageManagerService.TAG, "Stopping package " + pkgName, e);
+ }
List<UserInfo> users = getAllUsers(userManager);
- if (users != null) {
+ final int installUserId = installUser != null ? installUser.getIdentifier() : 0;
+ if (users != null && allowInstall) {
for (UserInfo user : users) {
- if ((installUser.getIdentifier() == UserHandle.USER_ALL
+ // By default we consider this app to be installed
+ // for the user if no user has been specified (which
+ // means to leave it at its original value, and the
+ // original default value is true), or we are being
+ // asked to install for all users, or this is the
+ // user we are installing for.
+ final boolean installed = installUser == null
+ || (installUserId == UserHandle.USER_ALL
&& !isAdbInstallDisallowed(userManager, user.id))
- || installUser.getIdentifier() == user.id) {
- boolean installed = pkgSetting.getInstalled(user.id);
- if (!installed) {
- pkgSetting.setInstalled(true, user.id);
- }
+ || installUserId == user.id;
+ pkgSetting.setUserState(user.id, 0, COMPONENT_ENABLED_STATE_DEFAULT,
+ installed,
+ true, // stopped,
+ true, // notLaunched
+ false, // hidden
+ false, // suspended
+ null, null, null,
+ false, // blockUninstall
+ INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0);
+ }
+ }
+ }
+ if (sharedUser != null) {
+ pkgSetting.appId = sharedUser.userId;
+ } else {
+ // Clone the setting here for disabled system packages
+ if (disabledPkg != null) {
+ // For disabled packages a new setting is created
+ // from the existing user id. This still has to be
+ // added to list of user id's
+ // Copy signatures from previous setting
+ pkgSetting.signatures = new PackageSignatures(disabledPkg.signatures);
+ pkgSetting.appId = disabledPkg.appId;
+ // Clone permissions
+ pkgSetting.getPermissionsState().copyFrom(disabledPkg.getPermissionsState());
+ // Clone component info
+ List<UserInfo> users = getAllUsers(userManager);
+ if (users != null) {
+ for (UserInfo user : users) {
+ final int userId = user.id;
+ pkgSetting.setDisabledComponentsCopy(
+ disabledPkg.getDisabledComponents(userId), userId);
+ pkgSetting.setEnabledComponentsCopy(
+ disabledPkg.getEnabledComponents(userId), userId);
}
}
}
@@ -966,6 +777,124 @@
return pkgSetting;
}
+ /**
+ * Updates the given package setting using the provided information.
+ * <p>
+ * WARNING: The provided PackageSetting object may be mutated.
+ */
+ static void updatePackageSetting(@NonNull PackageSetting pkgSetting,
+ @Nullable PackageSetting disabledPkg, @Nullable SharedUserSetting sharedUser,
+ @NonNull File codePath, @Nullable String legacyNativeLibraryPath,
+ @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi,
+ int pkgFlags, int pkgPrivateFlags, @Nullable List<String> childPkgNames,
+ @NonNull UserManagerService userManager) throws PackageManagerException {
+ final String pkgName = pkgSetting.name;
+ if (pkgSetting.sharedUser != sharedUser) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Package " + pkgName + " shared user changed from "
+ + (pkgSetting.sharedUser != null ? pkgSetting.sharedUser.name : "<nothing>")
+ + " to " + (sharedUser != null ? sharedUser.name : "<nothing>"));
+ throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+ "Updating application package " + pkgName + " failed");
+ }
+
+ if (!pkgSetting.codePath.equals(codePath)) {
+ // Check to see if its a disabled system app
+ if ((pkgSetting.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // This is an updated system app with versions in both system
+ // and data partition. Just let the most recent version
+ // take precedence.
+ Slog.w(PackageManagerService.TAG,
+ "Trying to update system app code path from "
+ + pkgSetting.codePathString + " to " + codePath.toString());
+ } else {
+ // Just a change in the code path is not an issue, but
+ // let's log a message about it.
+ Slog.i(PackageManagerService.TAG,
+ "Package " + pkgName + " codePath changed from "
+ + pkgSetting.codePath + " to " + codePath
+ + "; Retaining data and using new");
+
+ // The owner user's installed flag is set false
+ // when the application was installed by other user
+ // and the installed flag is not updated
+ // when the application is appended as system app later.
+ if ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0
+ && disabledPkg == null) {
+ List<UserInfo> allUserInfos = getAllUsers(userManager);
+ if (allUserInfos != null) {
+ for (UserInfo userInfo : allUserInfos) {
+ pkgSetting.setInstalled(true, userInfo.id);
+ }
+ }
+ }
+
+ /*
+ * Since we've changed paths, we need to prefer the new
+ * native library path over the one stored in the
+ * package settings since we might have moved from
+ * internal to external storage or vice versa.
+ */
+ pkgSetting.legacyNativeLibraryPathString = legacyNativeLibraryPath;
+ }
+ }
+ // If what we are scanning is a system (and possibly privileged) package,
+ // then make it so, regardless of whether it was previously installed only
+ // in the data partition.
+ pkgSetting.pkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM;
+ pkgSetting.pkgPrivateFlags |=
+ pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+ pkgSetting.primaryCpuAbiString = primaryCpuAbi;
+ pkgSetting.secondaryCpuAbiString = secondaryCpuAbi;
+ if (childPkgNames != null) {
+ pkgSetting.childPackageNames = new ArrayList<>(childPkgNames);
+ }
+ }
+
+ /**
+ * Registers a user ID with the system. Potentially allocates a new user ID.
+ * @throws PackageManagerException If a user ID could not be allocated.
+ */
+ void addUserToSettingLPw(PackageSetting p) throws PackageManagerException {
+ if (p.appId == 0) {
+ // Assign new user ID
+ p.appId = newUserIdLPw(p);
+ } else {
+ // Add new setting to list of user IDs
+ addUserIdLPw(p.appId, p, p.name);
+ }
+ if (p.appId < 0) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Package " + p.name + " could not be assigned a valid uid");
+ throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+ "Creating application package " + p.name + " failed");
+ }
+ }
+
+ /**
+ * Writes per-user package restrictions if the user state has changed. If the user
+ * state has not changed, this does nothing.
+ */
+ void writeUserRestrictions(PackageSetting newPackage, PackageSetting oldPackage) {
+ // package doesn't exist; do nothing
+ if (peekPackageLPr(newPackage.name) == null) {
+ return;
+ }
+ // no users defined; do nothing
+ final List<UserInfo> allUsers = getAllUsers(UserManagerService.getInstance());
+ if (allUsers == null) {
+ return;
+ }
+ for (UserInfo user : allUsers) {
+ final PackageUserState oldUserState = oldPackage == null
+ ? PackageSettingBase.DEFAULT_USER_STATE
+ : oldPackage.readUserState(user.id);
+ if (!oldUserState.equals(newPackage.readUserState(user.id))) {
+ writePackageRestrictionsLPr(user.id);
+ }
+ }
+ }
+
static boolean isAdbInstallDisallowed(UserManagerService userManager, int userId) {
return userManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES,
userId);
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index df51923..2af1bcb 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -151,6 +152,16 @@
return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
}
+ public void ensureVersionInfo() {
+ final PackageInfo pi = mShortcutUser.mService.getPackageInfoWithSignatures(
+ getPackageName(), getPackageUserId());
+ if (pi == null) {
+ Slog.w(TAG, "Package not found: " + getPackageName());
+ return;
+ }
+ getPackageInfo().updateVersionInfo(pi);
+ }
+
/**
* Persist.
*/
@@ -202,7 +213,7 @@
fromBackup ? ownerUserId
: ShortcutService.parseIntAttribute(parser, ATTR_LAUNCHER_USER_ID, ownerUserId);
- final ShortcutLauncher ret = new ShortcutLauncher(shortcutUser, launcherUserId,
+ final ShortcutLauncher ret = new ShortcutLauncher(shortcutUser, ownerUserId,
launcherPackageName, launcherUserId);
ArraySet<String> ids = null;
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 1acc955..d558b07 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -23,7 +23,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
-import android.os.Bundle;
import android.os.PersistableBundle;
import android.text.format.Formatter;
import android.util.ArrayMap;
@@ -145,30 +144,6 @@
return mPackageUid;
}
- /**
- * Called when a shortcut is about to be published. At this point we know the publisher
- * package
- * exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so
- * we do some initialization for the package.
- */
- private void ensurePackageVersionInfo() {
- // Make sure we have the version code for the app. We need the version code in
- // handlePackageUpdated().
- if (getPackageInfo().getVersionCode() < 0) {
- final ShortcutService s = mShortcutUser.mService;
-
- final PackageInfo pi = s.getPackageInfo(getPackageName(), getOwnerUserId());
- if (pi != null) {
- if (ShortcutService.DEBUG) {
- Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
- pi.versionCode));
- }
- getPackageInfo().updateVersionInfo(pi);
- s.scheduleSaveUser(getOwnerUserId());
- }
- }
- }
-
@Nullable
public Resources getPackageResources() {
return mShortcutUser.mService.injectGetResourcesForApplicationAsUser(
@@ -251,8 +226,6 @@
Preconditions.checkArgument(newShortcut.isEnabled(),
"add/setDynamicShortcuts() cannot publish disabled shortcuts");
- ensurePackageVersionInfo();
-
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index e7b66fc..4de15de 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -20,6 +20,7 @@
import android.content.pm.PackageInfo;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.backup.BackupUtils;
import libcore.io.Base64;
@@ -89,6 +90,7 @@
return mLastUpdateTime;
}
+ /** Set {@link #mVersionCode} and {@link #mLastUpdateTime} from a {@link PackageInfo}. */
public void updateVersionInfo(@NonNull PackageInfo pi) {
if (pi != null) {
mVersionCode = pi.versionCode;
@@ -119,7 +121,8 @@
return true;
}
- public static ShortcutPackageInfo generateForInstalledPackage(
+ @VisibleForTesting
+ public static ShortcutPackageInfo generateForInstalledPackageForTest(
ShortcutService s, String packageName, @UserIdInt int packageUserId) {
final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, packageUserId);
if (pi.signatures == null || pi.signatures.length == 0) {
@@ -132,7 +135,7 @@
return ret;
}
- public void refresh(ShortcutService s, ShortcutPackageItem pkg) {
+ public void refreshSignature(ShortcutService s, ShortcutPackageItem pkg) {
if (mIsShadow) {
s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName()
+ ", user=" + pkg.getOwnerUserId());
@@ -145,8 +148,6 @@
Slog.w(TAG, "Package not found: " + pkg.getPackageName());
return;
}
- mVersionCode = pi.versionCode;
- mLastUpdateTime = pi.lastUpdateTime;
mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 1780058..1f195a7 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -65,8 +65,7 @@
/**
* ID of the user who actually has this package running on. For {@link ShortcutPackage},
* this is the same thing as {@link #getOwnerUserId}, but if it's a {@link ShortcutLauncher} and
- * {@link #getOwnerUserId} is of a work profile, then this ID could be the user who owns the
- * profile.
+ * {@link #getOwnerUserId} is of work profile, then this ID is of the primary user.
*/
public int getPackageUserId() {
return mPackageUserId;
@@ -86,12 +85,12 @@
return mPackageInfo;
}
- public void refreshPackageInfoAndSave() {
+ public void refreshPackageSignatureAndSave() {
if (mPackageInfo.isShadow()) {
return; // Don't refresh for shadow user.
}
final ShortcutService s = mShortcutUser.mService;
- mPackageInfo.refresh(s, this);
+ mPackageInfo.refreshSignature(s, this);
s.scheduleSaveUser(getOwnerUserId());
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 2c61f75..13f558e 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1158,7 +1158,10 @@
}
}
- /** Return the per-user per-package state. */
+ /**
+ * Return the per-user per-package state. If the caller is a publisher, use
+ * {@link #getPackageShortcutsForPublisherLocked} instead.
+ */
@GuardedBy("mLock")
@NonNull
ShortcutPackage getPackageShortcutsLocked(
@@ -1166,6 +1169,16 @@
return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
}
+ /** Return the per-user per-package state. Use this when the caller is a publisher. */
+ @GuardedBy("mLock")
+ @NonNull
+ ShortcutPackage getPackageShortcutsForPublisherLocked(
+ @NonNull String packageName, @UserIdInt int userId) {
+ final ShortcutPackage ret = getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
+ ret.getUser().onCalledByPublisher(packageName);
+ return ret;
+ }
+
@GuardedBy("mLock")
@NonNull
ShortcutLauncher getLauncherShortcutsLocked(
@@ -1634,8 +1647,7 @@
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.getUser().onCalledByPublisher(packageName);
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
@@ -1686,8 +1698,7 @@
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.getUser().onCalledByPublisher(packageName);
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
@@ -1767,8 +1778,7 @@
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.getUser().onCalledByPublisher(packageName);
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
@@ -1817,8 +1827,7 @@
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.getUser().onCalledByPublisher(packageName);
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
@@ -1847,8 +1856,7 @@
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.getUser().onCalledByPublisher(packageName);
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
@@ -1870,8 +1878,7 @@
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.getUser().onCalledByPublisher(packageName);
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
@@ -1895,8 +1902,7 @@
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.getUser().onCalledByPublisher(packageName);
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.deleteAllDynamicShortcuts();
}
packageShortcutsChanged(packageName, userId);
@@ -1951,8 +1957,7 @@
final ArrayList<ShortcutInfo> ret = new ArrayList<>();
- final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.getUser().onCalledByPublisher(packageName);
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.findAll(ret, query, cloneFlags);
return new ParceledListSlice<>(ret);
@@ -1973,8 +1978,7 @@
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.getUser().onCalledByPublisher(packageName);
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
return mMaxUpdatesPerInterval - ps.getApiCallCount();
}
}
@@ -2013,8 +2017,7 @@
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.getUser().onCalledByPublisher(packageName);
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
if (ps.findShortcutById(shortcutId) == null) {
Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s",
@@ -3151,9 +3154,19 @@
return null;
}
- user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave());
+ // Update the signatures for all packages.
+ user.forAllPackageItems(spi -> spi.refreshPackageSignatureAndSave());
- // Then save.
+ // Set the version code for the launchers.
+ // We shouldn't do this for publisher packages, because we don't want to update the
+ // version code without rescanning the manifest.
+ user.forAllLaunchers(launcher -> launcher.ensureVersionInfo());
+
+ // Save to the filesystem.
+ scheduleSaveUser(userId);
+ saveDirtyInfo();
+
+ // Then create the backup payload.
final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
try {
saveUserInternalLocked(userId, os, /* forBackup */ true);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5176c06d..c1cb032 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -113,6 +113,7 @@
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
/**
@@ -130,6 +131,8 @@
private static final String LOG_TAG = "UserManagerService";
static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
private static final boolean DBG_WITH_STACKTRACE = false; // DO NOT SUBMIT WITH TRUE
+ // Can be used for manual testing of id recycling
+ private static final boolean RELEASE_DELETED_USER_ID = false; // DO NOT SUBMIT WITH TRUE
private static final String TAG_NAME = "name";
private static final String TAG_ACCOUNT = "account";
@@ -183,9 +186,15 @@
| UserInfo.FLAG_GUEST
| UserInfo.FLAG_DEMO;
- private static final int MIN_USER_ID = 10;
+ @VisibleForTesting
+ static final int MIN_USER_ID = 10;
// We need to keep process uid within Integer.MAX_VALUE.
- private static final int MAX_USER_ID = Integer.MAX_VALUE / UserHandle.PER_USER_RANGE;
+ @VisibleForTesting
+ static final int MAX_USER_ID = Integer.MAX_VALUE / UserHandle.PER_USER_RANGE;
+
+ // Max size of the queue of recently removed users
+ @VisibleForTesting
+ static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100;
private static final int USER_VERSION = 6;
@@ -312,10 +321,17 @@
/**
* Set of user IDs being actively removed. Removed IDs linger in this set
* for several seconds to work around a VFS caching issue.
+ * Use {@link #addRemovingUserIdLocked(int)} to add elements to this array
*/
@GuardedBy("mUsersLock")
private final SparseBooleanArray mRemovingUserIds = new SparseBooleanArray();
+ /**
+ * Queue of recently removed userIds. Used for recycling of userIds
+ */
+ @GuardedBy("mUsersLock")
+ private final LinkedList<Integer> mRecentlyRemovedIds = new LinkedList<>();
+
@GuardedBy("mUsersLock")
private int[] mUserIds;
@GuardedBy("mPackagesLock")
@@ -401,9 +417,10 @@
}
}
+ // TODO b/28848102 Add support for test dependencies injection
@VisibleForTesting
- UserManagerService(File dataDir) {
- this(null, null, new Object(), dataDir);
+ UserManagerService(Context context) {
+ this(context, null, new Object(), context.getCacheDir());
}
/**
@@ -472,7 +489,7 @@
UserInfo ui = mUsers.valueAt(i).info;
if ((ui.partial || ui.guestToRemove || ui.isEphemeral()) && i != 0) {
partials.add(ui);
- mRemovingUserIds.append(ui.id, true);
+ addRemovingUserIdLocked(ui.id);
ui.partial = true;
}
}
@@ -1791,11 +1808,7 @@
}
// Create the system user
UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags);
- UserData userData = new UserData();
- userData.info = system;
- synchronized (mUsersLock) {
- mUsers.put(system.id, userData);
- }
+ UserData userData = putUserInfo(system);
mNextSerialNumber = MIN_USER_ID;
mUserVersion = USER_VERSION;
@@ -2335,6 +2348,23 @@
return userInfo;
}
+ @VisibleForTesting
+ UserData putUserInfo(UserInfo userInfo) {
+ final UserData userData = new UserData();
+ userData.info = userInfo;
+ synchronized (mUsers) {
+ mUsers.put(userInfo.id, userData);
+ }
+ return userData;
+ }
+
+ @VisibleForTesting
+ void removeUserInfo(int userId) {
+ synchronized (mUsers) {
+ mUsers.remove(userId);
+ }
+ }
+
/**
* @hide
*/
@@ -2451,10 +2481,7 @@
return false;
}
- // We remember deleted user IDs to prevent them from being
- // reused during the current boot; they can still be reused
- // after a reboot.
- mRemovingUserIds.put(userHandle, true);
+ addRemovingUserIdLocked(userHandle);
}
try {
@@ -2501,6 +2528,19 @@
}
}
+ @VisibleForTesting
+ void addRemovingUserIdLocked(int userId) {
+ // We remember deleted user IDs to prevent them from being
+ // reused during the current boot; they can still be reused
+ // after a reboot or recycling of userIds.
+ mRemovingUserIds.put(userId, true);
+ mRecentlyRemovedIds.add(userId);
+ // Keep LRU queue of recently removed IDs for recycling
+ if (mRecentlyRemovedIds.size() > MAX_RECENTLY_REMOVED_IDS_SIZE) {
+ mRecentlyRemovedIds.removeFirst();
+ }
+ }
+
void finishRemoveUser(final int userHandle) {
if (DBG) Slog.i(LOG_TAG, "finishRemoveUser " + userHandle);
// Let other services shutdown any activity and clean up their state before completely
@@ -2586,6 +2626,11 @@
AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
userFile.delete();
updateUserIds();
+ if (RELEASE_DELETED_USER_ID) {
+ synchronized (mUsers) {
+ mRemovingUserIds.delete(userHandle);
+ }
+ }
}
private void sendProfileRemovedBroadcast(int parentUserId, int removedUserId) {
@@ -2966,20 +3011,39 @@
/**
* Returns the next available user id, filling in any holes in the ids.
- * TODO: May not be a good idea to recycle ids, in case it results in confusion
- * for data and battery stats collection, or unexpected cross-talk.
*/
- private int getNextAvailableId() {
+ @VisibleForTesting
+ int getNextAvailableId() {
+ int nextId;
synchronized (mUsersLock) {
- int i = MIN_USER_ID;
- while (i < MAX_USER_ID) {
- if (mUsers.indexOfKey(i) < 0 && !mRemovingUserIds.get(i)) {
- return i;
+ nextId = scanNextAvailableIdLocked();
+ if (nextId >= 0) {
+ return nextId;
+ }
+ // All ids up to MAX_USER_ID were used. Remove all mRemovingUserIds,
+ // except most recently removed
+ if (mRemovingUserIds.size() > 0) {
+ Slog.i(LOG_TAG, "All available IDs are used. Recycling LRU ids.");
+ mRemovingUserIds.clear();
+ for (Integer recentlyRemovedId : mRecentlyRemovedIds) {
+ mRemovingUserIds.put(recentlyRemovedId, true);
}
- i++;
+ nextId = scanNextAvailableIdLocked();
}
}
- throw new IllegalStateException("No user id available!");
+ if (nextId < 0) {
+ throw new IllegalStateException("No user id available!");
+ }
+ return nextId;
+ }
+
+ private int scanNextAvailableIdLocked() {
+ for (int i = MIN_USER_ID; i < MAX_USER_ID; i++) {
+ if (mUsers.indexOfKey(i) < 0 && !mRemovingUserIds.get(i)) {
+ return i;
+ }
+ }
+ return -1;
}
private String packageToRestrictionsFileName(String packageName) {
@@ -3284,6 +3348,10 @@
synchronized (mUsersLock) {
pw.println();
pw.println(" Device managed: " + mIsDeviceManaged);
+ if (mRemovingUserIds.size() > 0) {
+ pw.println();
+ pw.println(" Recently removed userIds: " + mRecentlyRemovedIds);
+ }
}
synchronized (mUserStates) {
pw.println(" Started users state: " + mUserStates);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index cfec1ba..6187978 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -647,6 +647,9 @@
// (See Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR.)
int mIncallPowerBehavior;
+ // Behavior of Back button while in-call and screen on
+ int mIncallBackBehavior;
+
Display mDisplay;
private int mDisplayRotation;
@@ -838,6 +841,9 @@
Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR), false, this,
UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.WAKE_GESTURE_ENABLED), false, this,
UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.System.getUriFor(
@@ -1060,7 +1066,7 @@
if (mBackKeyPressCounter <= PANIC_PRESS_BACK_COUNT) {
// This could be a multi-press. Wait a little bit longer to confirm.
Message msg = mHandler.obtainMessage(MSG_BACK_DELAYED_PRESS,
- mBackKeyPressCounter, 0, eventTime);
+ mBackKeyPressCounter, 0, eventTime);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout());
}
@@ -1069,6 +1075,27 @@
// Reset back long press state
cancelPendingBackKeyAction();
+ if (mHasFeatureWatch) {
+ TelecomManager telecomManager = getTelecommService();
+
+ if (telecomManager != null) {
+ if (telecomManager.isRinging()) {
+ // Pressing back while there's a ringing incoming
+ // call should silence the ringer.
+ telecomManager.silenceRinger();
+
+ // It should not prevent navigating away
+ return false;
+ } else if (
+ (mIncallBackBehavior & Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR_HANGUP) != 0
+ && telecomManager.isInCall()) {
+ // Otherwise, if "Back button ends call" is enabled,
+ // the Back button will hang up any current active call.
+ return telecomManager.endCall();
+ }
+ }
+ }
+
return handled;
}
@@ -2020,6 +2047,10 @@
Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT,
UserHandle.USER_CURRENT);
+ mIncallBackBehavior = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR,
+ Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR_DEFAULT,
+ UserHandle.USER_CURRENT);
// Configure wake gesture.
boolean wakeGestureEnabledSetting = Settings.Secure.getIntForUser(resolver,
@@ -5615,7 +5646,14 @@
@Override
public void onServiceDisconnected(ComponentName name) {
- notifyScreenshotError();
+ synchronized (mScreenshotLock) {
+ if (mScreenshotConnection != null) {
+ mContext.unbindService(mScreenshotConnection);
+ mScreenshotConnection = null;
+ mHandler.removeCallbacks(mScreenshotTimeout);
+ notifyScreenshotError();
+ }
+ }
}
};
if (mContext.bindServiceAsUser(serviceIntent, conn,
@@ -7849,7 +7887,7 @@
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
// Likewise we don't rotate seamlessly for 180 degree rotations
- // in this case the surfaces never resize, and our logic to
+ // in this case the surfaces never resize, and our logic to
// revert the transformations on size change will fail. We could
// fix this in the future with the "tagged" frames idea.
if (delta == Surface.ROTATION_180) {
@@ -8046,6 +8084,7 @@
pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive);
pw.print(prefix); pw.print("mEndcallBehavior="); pw.print(mEndcallBehavior);
pw.print(" mIncallPowerBehavior="); pw.print(mIncallPowerBehavior);
+ pw.print(" mIncallBackBehavior="); pw.print(mIncallBackBehavior);
pw.print(" mLongPressOnHomeBehavior="); pw.println(mLongPressOnHomeBehavior);
pw.print(prefix); pw.print("mLandscapeRotation="); pw.print(mLandscapeRotation);
pw.print(" mSeascapeRotation="); pw.println(mSeascapeRotation);
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ced84f6..a2b86e3 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2539,18 +2539,18 @@
boolean setDeviceIdleModeInternal(boolean enabled) {
synchronized (mLock) {
- if (mDeviceIdleMode != enabled) {
- mDeviceIdleMode = enabled;
- updateWakeLockDisabledStatesLocked();
- if (enabled) {
- EventLogTags.writeDeviceIdleOnPhase("power");
- } else {
- EventLogTags.writeDeviceIdleOffPhase("power");
- }
- return true;
+ if (mDeviceIdleMode == enabled) {
+ return false;
}
- return false;
+ mDeviceIdleMode = enabled;
+ updateWakeLockDisabledStatesLocked();
}
+ if (enabled) {
+ EventLogTags.writeDeviceIdleOnPhase("power");
+ } else {
+ EventLogTags.writeDeviceIdleOffPhase("power");
+ }
+ return true;
}
boolean setLightDeviceIdleModeInternal(boolean enabled) {
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index 0ae1717..90c711a 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -87,6 +87,11 @@
private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
+ // com.android.internal.R.string.low_internal_storage_view_text_no_boot
+ // hard codes 250MB in the message as the storage space required for the
+ // boot image.
+ private static final long BOOT_IMAGE_STORAGE_REQUIREMENT = 250 * 1024 * 1024;
+
private long mFreeMem; // on /data
private long mFreeMemAfterLastCacheClear; // on /data
private long mLastReportedFreeMem;
@@ -290,9 +295,10 @@
mLowMemFlag = false;
}
}
- if (!mLowMemFlag && !mIsBootImageOnDisk) {
+ if (!mLowMemFlag && !mIsBootImageOnDisk && mFreeMem < BOOT_IMAGE_STORAGE_REQUIREMENT) {
Slog.i(TAG, "No boot image on disk due to lack of space. Sending notification");
sendNotification();
+ mLowMemFlag = true;
}
if (mFreeMem < mMemFullThreshold) {
if (!mMemFullFlag) {
@@ -383,7 +389,7 @@
@Override
public boolean isMemoryLow() {
- return mLowMemFlag || !mIsBootImageOnDisk;
+ return mLowMemFlag;
}
@Override
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 5ddf283..305e47f 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -119,7 +118,7 @@
int stackClip) {
if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting animation in " + mAppToken
+ ": " + anim + " wxh=" + width + "x" + height
- + " isVisible=" + mAppToken.isVisible());
+ + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
animation = anim;
animating = false;
if (!anim.isInitialized()) {
@@ -141,13 +140,13 @@
}
// Start out animation gone if window is gone, or visible if window is visible.
transformation.clear();
- transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
+ transformation.setAlpha(mAppToken.hasContentToDisplay() ? 1 : 0);
hasTransformation = true;
mStackClip = stackClip;
this.mSkipFirstFrame = skipFirstFrame;
- if (!mAppToken.appFullscreen) {
+ if (!mAppToken.fillsParent()) {
anim.setBackgroundColor(0);
}
if (mClearProlongedAnimation) {
@@ -164,11 +163,11 @@
public void setDummyAnimation() {
if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting dummy animation in " + mAppToken
- + " isVisible=" + mAppToken.isVisible());
+ + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
animation = sDummyAnimation;
hasTransformation = true;
transformation.clear();
- transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
+ transformation.setAlpha(mAppToken.hasContentToDisplay() ? 1 : 0);
}
void setNullAnimation() {
@@ -384,8 +383,8 @@
return false;
}
- mAnimator.setAppLayoutChanges(this, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM,
- "AppWindowToken", displayId);
+ mAppToken.setAppLayoutChanges(
+ WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM, "AppWindowToken", displayId);
clearAnimation();
animating = false;
@@ -398,8 +397,7 @@
mService.moveInputMethodWindowsIfNeededLocked(true);
}
- if (DEBUG_ANIM) Slog.v(TAG,
- "Animation done in " + mAppToken
+ if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + mAppToken
+ ": reportedVisible=" + mAppToken.reportedVisible);
transformation.clear();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 47b5f3d..12872dc 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -17,10 +17,13 @@
package com.android.server.wm;
import static android.app.ActivityManager.StackId;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
@@ -40,7 +43,6 @@
import com.android.server.wm.WindowManagerService.H;
import android.annotation.NonNull;
-import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
@@ -64,7 +66,7 @@
* Version of WindowToken that is specifically for a particular application (or
* really activity) that is displaying windows.
*/
-class AppWindowToken extends WindowToken {
+class AppWindowToken extends WindowToken implements WindowManagerService.AppFreezeListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowToken" : TAG_WM;
// Non-null only for application tokens.
@@ -74,10 +76,10 @@
final boolean voiceInteraction;
+ // TODO: Use getParent instead?
Task mTask;
- // TODO: Have a fillParent variable in WindowContainer to this?
- boolean appFullscreen;
- int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ /** @see WindowContainer#fillsParent() */
+ private boolean mFillsParent;
boolean layoutConfigChanges;
boolean showForAllUsers;
int targetSdk;
@@ -190,7 +192,7 @@
mReportedVisibilityResults.reset();
for (int i = 0; i < count; i++) {
- final WindowState win = (WindowState) mChildren.get(i);
+ final WindowState win = mChildren.get(i);
win.updateReportedVisibility(mReportedVisibilityResults);
}
@@ -270,7 +272,7 @@
final int windowsCount = mChildren.size();
for (int i = 0; i < windowsCount; i++) {
- final WindowState win = (WindowState) mChildren.get(i);
+ final WindowState win = mChildren.get(i);
changed |= win.onAppVisibilityChanged(visible, runningAppAnimation);
}
@@ -306,7 +308,7 @@
}
for (int i = mChildren.size() - 1; i >= 0 && !delayed; i--) {
- if (((WindowState) mChildren.get(i)).isWindowAnimationSet()) {
+ if ((mChildren.get(i)).isWindowAnimationSet()) {
delayed = true;
}
}
@@ -336,7 +338,7 @@
int j = mChildren.size();
while (j > 0) {
j--;
- final WindowState win = (WindowState) mChildren.get(j);
+ final WindowState win = mChildren.get(j);
final int type = win.mAttrs.type;
// No need to loop through child window as base application and starting types can't be
// child windows.
@@ -359,18 +361,36 @@
}
@Override
+ boolean isVisible() {
+ if (hidden) {
+ // TODO: Should this be checking hiddenRequested instead of hidden?
+ return false;
+ }
+ return super.isVisible();
+ }
+
+ @Override
void removeIfPossible() {
mIsExiting = false;
removeAllWindows();
if (mTask != null) {
- mTask.detachChild(this);
+ mTask.mStack.mExitingAppTokens.remove(this);
+ mTask.removeChild(this);
}
}
+ @Override
+ boolean checkCompleteDeferredRemoval() {
+ if (mIsExiting) {
+ removeIfPossible();
+ }
+ return super.checkCompleteDeferredRemoval();
+ }
+
void clearAnimatingFlags() {
boolean wallpaperMightChange = false;
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState win = (WindowState) mChildren.get(i);
+ final WindowState win = mChildren.get(i);
wallpaperMightChange |= win.clearAnimatingFlags();
}
if (wallpaperMightChange) {
@@ -394,7 +414,7 @@
private void destroySurfaces(boolean cleanupOnResume) {
final DisplayContentList displayList = new DisplayContentList();
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState win = (WindowState) mChildren.get(i);
+ final WindowState win = mChildren.get(i);
final boolean destroyed = win.destroySurface(cleanupOnResume, mAppStopped);
if (destroyed) {
@@ -454,7 +474,7 @@
private boolean canRestoreSurfaces() {
for (int i = mChildren.size() -1; i >= 0; i--) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
if (w.canRestoreSurface()) {
return true;
}
@@ -464,7 +484,7 @@
private void clearWasVisibleBeforeClientHidden() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
w.clearWasVisibleBeforeClientHidden();
}
}
@@ -475,7 +495,7 @@
*/
boolean isAnimatingInvisibleWithSavedSurface() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
if (w.isAnimatingInvisibleWithSavedSurface()) {
return true;
}
@@ -489,7 +509,7 @@
*/
void stopUsingSavedSurfaceLocked() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
w.stopUsingSavedSurface();
}
destroySurfaces();
@@ -497,7 +517,7 @@
void markSavedSurfaceExiting() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
w.markSavedSurfaceExiting();
}
}
@@ -512,7 +532,7 @@
int interestingNotDrawn = -1;
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
interestingNotDrawn = w.restoreSavedSurfaceForInterestingWindow();
}
@@ -531,7 +551,7 @@
void destroySavedSurfaces() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState win = (WindowState) mChildren.get(i);
+ final WindowState win = mChildren.get(i);
win.destroySavedSurface();
}
}
@@ -564,7 +584,7 @@
void removeDeadWindows() {
for (int winNdx = mChildren.size() - 1; winNdx >= 0; --winNdx) {
- WindowState win = (WindowState) mChildren.get(winNdx);
+ WindowState win = mChildren.get(winNdx);
if (win.mAppDied) {
if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.w(TAG,
"removeDeadWindows: " + win);
@@ -580,7 +600,7 @@
for (int i = mChildren.size() - 1; i >= 0; i--) {
// No need to loop through child windows as the answer should be the same as that of the
// parent window.
- if (!((WindowState) mChildren.get(i)).mAppDied) {
+ if (!(mChildren.get(i)).mAppDied) {
return true;
}
}
@@ -592,7 +612,7 @@
"Marking app token " + this + " with replacing windows.");
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
w.setWillReplaceWindow(animate);
}
if (animate) {
@@ -609,7 +629,7 @@
if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Marking app token " + this
+ " with replacing child windows.");
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
w.setWillReplaceChildWindows();
}
}
@@ -619,14 +639,14 @@
"Resetting app token " + this + " of replacing window marks.");
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
w.clearWillReplaceWindow();
}
}
void requestUpdateWallpaperIfNeeded() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
w.requestUpdateWallpaperIfNeeded();
}
}
@@ -667,7 +687,7 @@
boolean gotReplacementWindow = false;
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState candidate = (WindowState) mChildren.get(i);
+ final WindowState candidate = mChildren.get(i);
gotReplacementWindow |= candidate.setReplacementWindowIfNeeded(w);
}
@@ -679,7 +699,7 @@
boolean waitingForReplacement() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState candidate = (WindowState) mChildren.get(i);
+ final WindowState candidate = mChildren.get(i);
if (candidate.waitingForReplacement()) {
return true;
}
@@ -689,7 +709,7 @@
void onWindowReplacementTimeout() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- ((WindowState) mChildren.get(i)).onWindowReplacementTimeout();
+ (mChildren.get(i)).onWindowReplacementTimeout();
}
}
@@ -710,7 +730,7 @@
if (mTask.mPreparedFrozenMergedConfig.equals(Configuration.EMPTY)) {
// We didn't call prepareFreezingBounds on the task, so use the current value.
- final Configuration config = new Configuration(mService.mCurConfiguration);
+ final Configuration config = new Configuration(mService.mGlobalConfiguration);
config.updateFrom(mTask.mOverrideConfig);
mFrozenMergedConfig.offer(config);
} else {
@@ -730,7 +750,7 @@
mFrozenMergedConfig.remove();
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState win = (WindowState) mChildren.get(i);
+ final WindowState win = mChildren.get(i);
win.onUnfreezeBounds();
}
mService.mWindowPlacerLocked.performSurfacePlacement();
@@ -780,13 +800,13 @@
void resetJustMovedInStack() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
- ((WindowState) mChildren.get(i)).resetJustMovedInStack();
+ (mChildren.get(i)).resetJustMovedInStack();
}
}
void notifyMovedInStack() {
for (int winNdx = mChildren.size() - 1; winNdx >= 0; --winNdx) {
- final WindowState win = (WindowState) mChildren.get(winNdx);
+ final WindowState win = mChildren.get(winNdx);
win.notifyMovedInStack();
}
}
@@ -795,7 +815,7 @@
final WindowAnimator windowAnimator = mAppAnimator.mAnimator;
for (int i = mChildren.size() - 1; i >= 0; i--) {
// Child windows will be on the same display as their parents.
- if (displayId == ((WindowState) mChildren.get(i)).getDisplayId()) {
+ if (displayId == (mChildren.get(i)).getDisplayId()) {
windowAnimator.setPendingLayoutChanges(displayId, changes);
if (DEBUG_LAYOUT_REPEATS) {
mService.mWindowPlacerLocked.debugLayoutRepeats(
@@ -808,7 +828,7 @@
void removeReplacedWindowIfNeeded(WindowState replacement) {
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState win = (WindowState) mChildren.get(i);
+ final WindowState win = mChildren.get(i);
if (win.removeReplacedWindowIfNeeded(replacement)) {
return;
}
@@ -822,6 +842,7 @@
if (!hiddenRequested) {
if (!mAppAnimator.freezingScreen) {
mAppAnimator.freezingScreen = true;
+ mService.registerAppFreezeListener(this);
mAppAnimator.lastFreezeDuration = 0;
mService.mAppsFreezingScreen++;
if (mService.mAppsFreezingScreen == 1) {
@@ -832,7 +853,7 @@
}
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
w.onStartFreezingScreen();
}
}
@@ -846,12 +867,13 @@
final int count = mChildren.size();
boolean unfrozeWindows = false;
for (int i = 0; i < count; i++) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
unfrozeWindows |= w.onStopFreezingScreen();
}
if (force || unfrozeWindows) {
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "No longer freezing: " + this);
mAppAnimator.freezingScreen = false;
+ mService.unregisterAppFreezeListener(this);
mAppAnimator.lastFreezeDuration =
(int)(SystemClock.elapsedRealtime() - mService.mDisplayFreezeTime);
mService.mAppsFreezingScreen--;
@@ -865,6 +887,12 @@
}
}
+ @Override
+ public void onAppFreezeTimeout() {
+ Slog.w(TAG_WM, "Force clearing freeze: " + this);
+ stopFreezingScreen(true, true);
+ }
+
boolean transferStartingWindow(IBinder transferFrom) {
final AppWindowToken fromToken = mService.findAppWindowToken(transferFrom);
if (fromToken == null) {
@@ -972,25 +1000,137 @@
final int windowsCount = mChildren.size();
for (int j = 0; j < windowsCount; j++) {
- ((WindowState) mChildren.get(j)).addWinAnimatorToList(allAppWinAnimators);
+ (mChildren.get(j)).addWinAnimatorToList(allAppWinAnimators);
}
}
@Override
+ void onAppTransitionDone() {
+ sendingToBottom = false;
+ }
+
+ /**
+ * We override because this class doesn't want its children affecting its reported orientation
+ * in anyway.
+ */
+ @Override
+ int getOrientation() {
+ if (hidden || hiddenRequested) {
+ return SCREEN_ORIENTATION_UNSET;
+ }
+ return mOrientation;
+ }
+
+ @Override
+ void checkAppWindowsReadyToShow(int displayId) {
+ if (allDrawn == mAppAnimator.allDrawn) {
+ return;
+ }
+
+ mAppAnimator.allDrawn = allDrawn;
+ if (!allDrawn) {
+ return;
+ }
+
+ // The token has now changed state to having all windows shown... what to do, what to do?
+ if (mAppAnimator.freezingScreen) {
+ mAppAnimator.showAllWindowsLocked();
+ stopFreezingScreen(false, true);
+ if (DEBUG_ORIENTATION) Slog.i(TAG,
+ "Setting mOrientationChangeComplete=true because wtoken " + this
+ + " numInteresting=" + numInterestingWindows + " numDrawn=" + numDrawnWindows);
+ // This will set mOrientationChangeComplete and cause a pass through layout.
+ setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
+ "checkAppWindowsReadyToShow: freezingScreen", displayId);
+ } else {
+ setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow", displayId);
+
+ // We can now show all of the drawn windows!
+ if (!mService.mOpeningApps.contains(this)) {
+ mService.mAnimator.orAnimating(mAppAnimator.showAllWindowsLocked());
+ }
+ }
+ }
+
+ @Override
+ void updateAllDrawn(int displayId) {
+ final DisplayContent displayContent = mService.getDisplayContentLocked(displayId);
+
+ if (!allDrawn) {
+ final int numInteresting = numInterestingWindows;
+ if (numInteresting > 0 && numDrawnWindows >= numInteresting) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG, "allDrawn: " + this
+ + " interesting=" + numInteresting + " drawn=" + numDrawnWindows);
+ allDrawn = true;
+ // Force an additional layout pass where
+ // WindowStateAnimator#commitFinishDrawingLocked() will call performShowLocked().
+ displayContent.layoutNeeded = true;
+ mService.mH.obtainMessage(NOTIFY_ACTIVITY_DRAWN, token).sendToTarget();
+ }
+ }
+ if (!allDrawnExcludingSaved) {
+ int numInteresting = numInterestingWindowsExcludingSaved;
+ if (numInteresting > 0 && numDrawnWindowsExcludingSaved >= numInteresting) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG, "allDrawnExcludingSaved: " + this
+ + " interesting=" + numInteresting
+ + " drawn=" + numDrawnWindowsExcludingSaved);
+ allDrawnExcludingSaved = true;
+ displayContent.layoutNeeded = true;
+ if (isAnimatingInvisibleWithSavedSurface()
+ && !mService.mFinishedEarlyAnim.contains(this)) {
+ mService.mFinishedEarlyAnim.add(this);
+ }
+ }
+ }
+ }
+
+ @Override
+ void stepAppWindowsAnimation(long currentTime, int displayId) {
+ mAppAnimator.wasAnimating = mAppAnimator.animating;
+ if (mAppAnimator.stepAnimationLocked(currentTime, displayId)) {
+ mAppAnimator.animating = true;
+ mService.mAnimator.setAnimating(true);
+ mService.mAnimator.mAppWindowAnimating = true;
+ } else if (mAppAnimator.wasAnimating) {
+ // stopped animating, do one more pass through the layout
+ setAppLayoutChanges(
+ FINISH_LAYOUT_REDO_WALLPAPER, "appToken " + this + " done", displayId);
+ if (DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating " + this);
+ }
+ }
+
+ @Override
+ int rebuildWindowList(DisplayContent dc, int addIndex) {
+ if (mIsExiting && !waitingForReplacement()) {
+ return addIndex;
+ }
+ return super.rebuildWindowList(dc, addIndex);
+ }
+
+ @Override
AppWindowToken asAppWindowToken() {
// I am an app window token!
return this;
}
@Override
+ boolean fillsParent() {
+ return mFillsParent;
+ }
+
+ void setFillsParent(boolean fillsParent) {
+ mFillsParent = fillsParent;
+ }
+
+ @Override
void dump(PrintWriter pw, String prefix) {
super.dump(pw, prefix);
if (appToken != null) {
pw.print(prefix); pw.print("app=true voiceInteraction="); pw.println(voiceInteraction);
}
pw.print(prefix); pw.print("task="); pw.println(mTask);
- pw.print(prefix); pw.print(" appFullscreen="); pw.print(appFullscreen);
- pw.print(" requestedOrientation="); pw.println(requestedOrientation);
+ pw.print(prefix); pw.print(" mFillsParent="); pw.print(mFillsParent);
+ pw.print(" mOrientation="); pw.println(mOrientation);
pw.print(prefix); pw.print("hiddenRequested="); pw.print(hiddenRequested);
pw.print(" clientHidden="); pw.print(clientHidden);
pw.print(" reportedDrawn="); pw.print(reportedDrawn);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e7a6747..979eb9d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -17,13 +17,26 @@
package com.android.server.wm;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
@@ -32,15 +45,20 @@
import android.graphics.Region;
import android.graphics.Region.Op;
import android.hardware.display.DisplayManagerInternal;
+import android.os.Debug;
import android.util.DisplayMetrics;
import android.util.Slog;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.IWindow;
import android.view.Surface;
import android.view.animation.Animation;
+import com.android.internal.util.FastPrintWriter;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Arrays;
class DisplayContentList extends ArrayList<DisplayContent> {
}
@@ -73,7 +91,7 @@
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
Rect mBaseDisplayRect = new Rect();
- Rect mContentRect = new Rect();
+ private Rect mContentRect = new Rect();
// Accessed directly by all users.
boolean layoutNeeded;
@@ -95,16 +113,13 @@
TaskTapPointerEventListener mTapDetector;
/** Detect user tapping outside of current focused stack bounds .*/
- Region mTouchExcludeRegion = new Region();
+ private Region mTouchExcludeRegion = new Region();
/** Save allocating when calculating rects */
private final Rect mTmpRect = new Rect();
private final Rect mTmpRect2 = new Rect();
private final Region mTmpRegion = new Region();
- /** For gathering Task objects in order. */
- final ArrayList<Task> mTmpTaskHistory = new ArrayList<Task>();
-
final WindowManagerService mService;
/** Remove this display when animation on it has completed. */
@@ -116,6 +131,14 @@
final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
+ /** Used when rebuilding window list to keep track of windows that have been removed. */
+ private WindowState[] mRebuildTmp = new WindowState[20];
+
+ private final TaskForResizePointSearchResult mTmpTaskForResizePointSearchResult =
+ new TaskForResizePointSearchResult();
+ private final GetWindowOnDisplaySearchResult mTmpGetWindowOnDisplaySearchResult =
+ new GetWindowOnDisplaySearchResult();
+
/**
* @param display May not be null.
* @param service You know.
@@ -171,19 +194,6 @@
return mStacks;
}
- /**
- * Retrieve the tasks on this display in stack order from the bottommost TaskStack up.
- * @return All the Tasks, in order, on this display.
- */
- ArrayList<Task> getTasks() {
- mTmpTaskHistory.clear();
- final int numStacks = mStacks.size();
- for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- mTmpTaskHistory.addAll(mStacks.get(stackNdx).getTasks());
- }
- return mTmpTaskHistory;
- }
-
TaskStack getHomeStack() {
if (mHomeStack == null && mDisplayId == Display.DEFAULT_DISPLAY) {
Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
@@ -201,6 +211,81 @@
return null;
}
+ void checkAppWindowsReadyToShow() {
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mStacks.get(i);
+ stack.checkAppWindowsReadyToShow(mDisplayId);
+ }
+ }
+
+ void updateAllDrawn() {
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mStacks.get(i);
+ stack.updateAllDrawn(mDisplayId);
+ }
+ }
+
+ void stepAppWindowsAnimation(long currentTime) {
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mStacks.get(i);
+ stack.stepAppWindowsAnimation(currentTime, mDisplayId);
+ }
+ }
+
+ void onAppTransitionDone() {
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mStacks.get(i);
+ stack.onAppTransitionDone();
+ }
+
+ rebuildAppWindowList();
+ }
+
+ int getOrientation() {
+ // TODO: Most of the logic here can be removed once this class is converted to use
+ // WindowContainer which has an abstract implementation of getOrientation that
+ // should cover this.
+ if (mService.isStackVisibleLocked(DOCKED_STACK_ID)
+ || mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
+ // Apps and their containers are not allowed to specify an orientation while the docked
+ // or freeform stack is visible...except for the home stack/task if the docked stack is
+ // minimized and it actually set something.
+ if (mHomeStack != null && mHomeStack.isVisible()
+ && mDividerControllerLocked.isMinimizedDock()) {
+ final int orientation = mHomeStack.getOrientation();
+ if (orientation != SCREEN_ORIENTATION_UNSET) {
+ return orientation;
+ }
+ }
+ return SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mStacks.get(i);
+ if (!stack.isVisible()) {
+ continue;
+ }
+
+ final int orientation = stack.getOrientation();
+
+ if (orientation == SCREEN_ORIENTATION_BEHIND) {
+ continue;
+ }
+
+ if (orientation != SCREEN_ORIENTATION_UNSET) {
+ if (stack.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ return orientation;
+ }
+ }
+ }
+
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+ "No app is requesting an orientation, return " + mService.mLastOrientation);
+ // The next app has not been requested to be visible, so we keep the current orientation
+ // to prevent freezing/unfreezing the display too early.
+ return mService.mLastOrientation;
+ }
+
void updateDisplayInfo() {
mDisplay.getDisplayInfo(mDisplayInfo);
mDisplay.getMetrics(mDisplayMetrics);
@@ -286,6 +371,19 @@
mStacks.add(addIndex, stack);
}
+ // TODO: Don't forget to switch to WC.removeChild
+ void detachChild(TaskStack stack) {
+ detachStack(stack);
+ if (stack.detachFromDisplay()) {
+ mService.mWindowPlacerLocked.requestTraversal();
+ }
+ if (stack.mStackId == DOCKED_STACK_ID) {
+ mService.getDefaultDisplayContentLocked().mDividerControllerLocked
+ .notifyDockedStackExistsChanged(false);
+ }
+ }
+
+ // TODO: See about removing this by untangling the use case in WMS.attachStack()
void detachStack(TaskStack stack) {
mDimLayerController.removeDimLayerUser(stack);
mStacks.remove(stack);
@@ -301,27 +399,10 @@
int taskIdFromPoint(int x, int y) {
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
- TaskStack stack = mStacks.get(stackNdx);
- stack.getBounds(mTmpRect);
- if (!mTmpRect.contains(x, y) || stack.isAdjustedForMinimizedDockedStack()) {
- continue;
- }
- final ArrayList<Task> tasks = stack.getTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = tasks.get(taskNdx);
- final WindowState win = task.getTopVisibleAppMainWindow();
- if (win == null) {
- continue;
- }
- // We need to use the task's dim bounds (which is derived from the visible
- // bounds of its apps windows) for any touch-related tests. Can't use
- // the task's original bounds because it might be adjusted to fit the
- // content frame. For example, the presence of the IME adjusting the
- // windows frames when the app window is the IME target.
- task.getDimBounds(mTmpRect);
- if (mTmpRect.contains(x, y)) {
- return task.mTaskId;
- }
+ final TaskStack stack = mStacks.get(stackNdx);
+ final int taskId = stack.taskIdFromPoint(x, y);
+ if (taskId != -1) {
+ return taskId;
}
}
return -1;
@@ -331,37 +412,18 @@
* Find the task whose outside touch area (for resizing) (x, y) falls within.
* Returns null if the touch doesn't fall into a resizing area.
*/
- Task findTaskForControlPoint(int x, int y) {
+ Task findTaskForResizePoint(int x, int y) {
final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
+ mTmpTaskForResizePointSearchResult.reset();
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
TaskStack stack = mStacks.get(stackNdx);
if (!StackId.isTaskResizeAllowed(stack.mStackId)) {
- break;
+ return null;
}
- final ArrayList<Task> tasks = stack.getTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = tasks.get(taskNdx);
- if (task.isFullscreen()) {
- return null;
- }
- // We need to use the task's dim bounds (which is derived from the visible
- // bounds of its apps windows) for any touch-related tests. Can't use
- // the task's original bounds because it might be adjusted to fit the
- // content frame. One example is when the task is put to top-left quadrant,
- // the actual visible area would not start at (0,0) after it's adjusted
- // for the status bar.
- task.getDimBounds(mTmpRect);
- mTmpRect.inset(-delta, -delta);
- if (mTmpRect.contains(x, y)) {
- mTmpRect.inset(delta, delta);
- if (!mTmpRect.contains(x, y)) {
- return task;
- }
- // User touched inside the task. No need to look further,
- // focus transfer will be handled in ACTION_UP.
- return null;
- }
+ stack.findTaskForResizePoint(x, y, delta, mTmpTaskForResizePointSearchResult);
+ if (mTmpTaskForResizePointSearchResult.searchDone) {
+ return mTmpTaskForResizePointSearchResult.taskForResize;
}
}
return null;
@@ -370,54 +432,16 @@
void setTouchExcludeRegion(Task focusedTask) {
mTouchExcludeRegion.set(mBaseDisplayRect);
final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
- boolean addBackFocusedTask = false;
+ mTmpRect2.setEmpty();
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
- TaskStack stack = mStacks.get(stackNdx);
- final ArrayList<Task> tasks = stack.getTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = tasks.get(taskNdx);
- AppWindowToken token = task.getTopVisibleAppToken();
- if (token == null || !token.isVisible()) {
- continue;
- }
-
- /**
- * Exclusion region is the region that TapDetector doesn't care about.
- * Here we want to remove all non-focused tasks from the exclusion region.
- * We also remove the outside touch area for resizing for all freeform
- * tasks (including the focused).
- *
- * We save the focused task region once we find it, and add it back at the end.
- */
-
- task.getDimBounds(mTmpRect);
-
- if (task == focusedTask) {
- addBackFocusedTask = true;
- mTmpRect2.set(mTmpRect);
- }
-
- final boolean isFreeformed = task.inFreeformWorkspace();
- if (task != focusedTask || isFreeformed) {
- if (isFreeformed) {
- // If the task is freeformed, enlarge the area to account for outside
- // touch area for resize.
- mTmpRect.inset(-delta, -delta);
- // Intersect with display content rect. If we have system decor (status bar/
- // navigation bar), we want to exclude that from the tap detection.
- // Otherwise, if the app is partially placed under some system button (eg.
- // Recents, Home), pressing that button would cause a full series of
- // unwanted transfer focus/resume/pause, before we could go home.
- mTmpRect.intersect(mContentRect);
- }
- mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
- }
- }
+ final TaskStack stack = mStacks.get(stackNdx);
+ stack.setTouchExcludeRegion(
+ focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2);
}
// If we removed the focused task above, add it back and only leave its
// outside touch area in the exclusion. TapDectector is not interested in
// any touch inside the focused task itself.
- if (addBackFocusedTask) {
+ if (!mTmpRect2.isEmpty()) {
mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
}
final WindowState inputMethod = mService.mInputMethodWindow;
@@ -457,6 +481,8 @@
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
mStacks.get(stackNdx).switchUser();
}
+
+ rebuildAppWindowList();
}
void resetAnimationBackgroundAnimator() {
@@ -498,32 +524,18 @@
return false;
}
- void checkForDeferredActions() {
- boolean animating = false;
+ /** Returns true if a removal action is still being deferred. */
+ boolean checkCompleteDeferredRemoval() {
+ boolean stillDeferringRemoval = false;
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
final TaskStack stack = mStacks.get(stackNdx);
- if (stack.isAnimating()) {
- animating = true;
- } else {
- if (stack.mDeferDetach) {
- mService.detachStackLocked(this, stack);
- }
- final ArrayList<Task> tasks = stack.getTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = tasks.get(taskNdx);
- AppTokenList tokens = task.mAppTokens;
- for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
- AppWindowToken wtoken = tokens.get(tokenNdx);
- if (wtoken.mIsExiting) {
- wtoken.removeIfPossible();
- }
- }
- }
- }
+ stillDeferringRemoval |= stack.checkCompleteDeferredRemoval();
}
- if (!animating && mDeferredRemoval) {
+ if (!stillDeferringRemoval && mDeferredRemoval) {
mService.onDisplayRemoved(mDisplayId);
+ return false;
}
+ return true;
}
void rotateBounds(int oldRotation, int newRotation, Rect bounds) {
@@ -617,7 +629,11 @@
@Override
public String toString() {
- return "Display " + mDisplayId + " info=" + mDisplayInfo + " stacks=" + mStacks;
+ return getName() + " stacks=" + mStacks;
+ }
+
+ String getName() {
+ return "Display " + mDisplayId + " info=" + mDisplayInfo;
}
/**
@@ -637,9 +653,7 @@
return (stack != null && stack.isVisible(true /* ignoreKeyguard */)) ? stack : null;
}
- /**
- * Find the visible, touch-deliverable window under the given point
- */
+ /** Find the visible, touch-deliverable window under the given point */
WindowState getTouchableWinAtPointLocked(float xf, float yf) {
WindowState touchedWin = null;
final int x = (int) xf;
@@ -715,4 +729,454 @@
}
}
}
+
+ WindowState findFocusedWindow() {
+ final AppWindowToken focusedApp = mService.mFocusedApp;
+
+ for (int i = mWindows.size() - 1; i >= 0; i--) {
+ final WindowState win = mWindows.get(i);
+
+ if (DEBUG_FOCUS) Slog.v(TAG_WM, "Looking for focus: " + i + " = " + win
+ + ", flags=" + win.mAttrs.flags + ", canReceive=" + win.canReceiveKeys());
+
+ if (!win.canReceiveKeys()) {
+ continue;
+ }
+
+ final AppWindowToken wtoken = win.mAppToken;
+
+ // If this window's application has been removed, just skip it.
+ if (wtoken != null && (wtoken.removed || wtoken.sendingToBottom)) {
+ if (DEBUG_FOCUS) Slog.v(TAG_WM, "Skipping " + wtoken + " because "
+ + (wtoken.removed ? "removed" : "sendingToBottom"));
+ continue;
+ }
+
+ if (focusedApp == null) {
+ if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp=null"
+ + " using new focus @ " + i + " = " + win);
+ return win;
+ }
+
+ if (!focusedApp.windowsAreFocusable()) {
+ // Current focused app windows aren't focusable...
+ if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp windows not"
+ + " focusable using new focus @ " + i + " = " + win);
+ return win;
+ }
+
+ // Descend through all of the app tokens and find the first that either matches
+ // win.mAppToken (return win) or mFocusedApp (return null).
+ if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING) {
+ final TaskStack focusedAppStack = focusedApp.mTask.mStack;
+ final TaskStack appStack = wtoken.mTask.mStack;
+
+ // TODO: Use WindowContainer.compareTo() once everything is using WindowContainer
+ if ((focusedAppStack == appStack
+ && appStack.isFirstGreaterThanSecond(focusedApp, wtoken))
+ || mStacks.indexOf(focusedAppStack) > mStacks.indexOf(appStack)) {
+ // App stack below focused app stack. No focus for you!!!
+ if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM,
+ "findFocusedWindow: Reached focused app=" + focusedApp);
+ return null;
+ }
+ }
+
+ if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Found new focus @ "
+ + i + " = " + win);
+ return win;
+ }
+
+ if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows.");
+ return null;
+ }
+
+ int addAppWindowToWindowList(final WindowState win) {
+ final IWindow client = win.mClient;
+
+ WindowList tokenWindowList = getTokenWindowsOnDisplay(win.mToken);
+ if (!tokenWindowList.isEmpty()) {
+ return addAppWindowExisting(win, tokenWindowList);
+ }
+
+ // No windows from this token on this display
+ if (mService.localLOGV) Slog.v(TAG_WM, "Figuring out where to add app window "
+ + client.asBinder() + " (token=" + this + ")");
+
+ final WindowToken wToken = win.mToken;
+
+ // Figure out where the window should go, based on the order of applications.
+ mTmpGetWindowOnDisplaySearchResult.reset();
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mStacks.get(i);
+ stack.getWindowOnDisplayBeforeToken(this, wToken, mTmpGetWindowOnDisplaySearchResult);
+ if (mTmpGetWindowOnDisplaySearchResult.reachedToken) {
+ // We have reach the token we are interested in. End search.
+ break;
+ }
+ }
+
+ WindowState pos = mTmpGetWindowOnDisplaySearchResult.foundWindow;
+
+ // We now know the index into the apps. If we found an app window above, that gives us the
+ // position; else we need to look some more.
+ if (pos != null) {
+ // Move behind any windows attached to this one.
+ final WindowToken atoken = mService.mTokenMap.get(pos.mClient.asBinder());
+ if (atoken != null) {
+ tokenWindowList = getTokenWindowsOnDisplay(atoken);
+ final int NC = tokenWindowList.size();
+ if (NC > 0) {
+ WindowState bottom = tokenWindowList.get(0);
+ if (bottom.mSubLayer < 0) {
+ pos = bottom;
+ }
+ }
+ }
+ addWindowToListBefore(win, pos);
+ return 0;
+ }
+
+ // Continue looking down until we find the first token that has windows on this display.
+ mTmpGetWindowOnDisplaySearchResult.reset();
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mStacks.get(i);
+ stack.getWindowOnDisplayAfterToken(this, wToken, mTmpGetWindowOnDisplaySearchResult);
+ if (mTmpGetWindowOnDisplaySearchResult.foundWindow != null) {
+ // We have found a window after the token. End search.
+ break;
+ }
+ }
+
+ pos = mTmpGetWindowOnDisplaySearchResult.foundWindow;
+
+ if (pos != null) {
+ // Move in front of any windows attached to this one.
+ final WindowToken atoken = mService.mTokenMap.get(pos.mClient.asBinder());
+ if (atoken != null) {
+ final WindowState top = atoken.getTopWindow();
+ if (top != null && top.mSubLayer >= 0) {
+ pos = top;
+ }
+ }
+ addWindowToListAfter(win, pos);
+ return 0;
+ }
+
+ // Just search for the start of this layer.
+ final int myLayer = win.mBaseLayer;
+ int i;
+ for (i = mWindows.size() - 1; i >= 0; --i) {
+ final WindowState w = mWindows.get(i);
+ // Dock divider shares the base layer with application windows, but we want to always
+ // keep it above the application windows. The sharing of the base layer is intended
+ // for window animations, which need to be above the dock divider for the duration
+ // of the animation.
+ if (w.mBaseLayer <= myLayer && w.mAttrs.type != TYPE_DOCK_DIVIDER) {
+ break;
+ }
+ }
+ if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "Based on layer: Adding window " + win + " at " + (i + 1) + " of "
+ + mWindows.size());
+ mWindows.add(i + 1, win);
+ mService.mWindowsChanged = true;
+ return 0;
+ }
+
+ /** Adds this non-app window to the window list. */
+ void addNonAppWindowToWindowList(WindowState win) {
+ // Figure out where window should go, based on layer.
+ int i;
+ for (i = mWindows.size() - 1; i >= 0; i--) {
+ final WindowState otherWin = mWindows.get(i);
+ if (otherWin.getBaseType() != TYPE_WALLPAPER && otherWin.mBaseLayer <= win.mBaseLayer) {
+ // Wallpaper wanders through the window list, for example to position itself
+ // directly behind keyguard. Because of this it will break the ordering based on
+ // WindowState.mBaseLayer. There might windows with higher mBaseLayer behind it and
+ // we don't want the new window to appear above them. An example of this is adding
+ // of the docked stack divider. Consider a scenario with the following ordering (top
+ // to bottom): keyguard, wallpaper, assist preview, apps. We want the dock divider
+ // to land below the assist preview, so the dock divider must ignore the wallpaper,
+ // with which it shares the base layer.
+ break;
+ }
+ }
+
+ i++;
+ if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "Free window: Adding window " + this + " at " + i + " of " + mWindows.size());
+ mWindows.add(i, win);
+ mService.mWindowsChanged = true;
+ }
+
+ void addChildWindowToWindowList(WindowState win) {
+ final WindowState parentWindow = win.getParentWindow();
+
+ WindowList windowsOnSameDisplay = getTokenWindowsOnDisplay(win.mToken);
+
+ // Figure out this window's ordering relative to the parent window.
+ final int wCount = windowsOnSameDisplay.size();
+ final int sublayer = win.mSubLayer;
+ int largestSublayer = Integer.MIN_VALUE;
+ WindowState windowWithLargestSublayer = null;
+ int i;
+ for (i = 0; i < wCount; i++) {
+ WindowState w = windowsOnSameDisplay.get(i);
+ final int wSublayer = w.mSubLayer;
+ if (wSublayer >= largestSublayer) {
+ largestSublayer = wSublayer;
+ windowWithLargestSublayer = w;
+ }
+ if (sublayer < 0) {
+ // For negative sublayers, we go below all windows in the same sublayer.
+ if (wSublayer >= sublayer) {
+ addWindowToListBefore(win, wSublayer >= 0 ? parentWindow : w);
+ break;
+ }
+ } else {
+ // For positive sublayers, we go above all windows in the same sublayer.
+ if (wSublayer > sublayer) {
+ addWindowToListBefore(win, w);
+ break;
+ }
+ }
+ }
+ if (i >= wCount) {
+ if (sublayer < 0) {
+ addWindowToListBefore(win, parentWindow);
+ } else {
+ addWindowToListAfter(win,
+ largestSublayer >= 0 ? windowWithLargestSublayer : parentWindow);
+ }
+ }
+ }
+
+ /**
+ * Z-orders the display window list so that:
+ * <ul>
+ * <li>Any windows that are currently below the wallpaper window stay below the wallpaper
+ * window.
+ * <li>Exiting application windows are at the bottom, but above the wallpaper window.
+ * <li>All other application windows are above the exiting application windows and ordered based
+ * on the ordering of their stacks and tasks on the display.
+ * <li>Non-application windows are at the very top.
+ * </ul>
+ * <p>
+ * NOTE: This isn't a complete picture of what the user see. Further manipulation of the window
+ * surface layering is done in {@link WindowLayersController}.
+ */
+ void rebuildAppWindowList() {
+ int count = mWindows.size();
+ int i;
+ int lastBelow = -1;
+ int numRemoved = 0;
+
+ if (mRebuildTmp.length < count) {
+ mRebuildTmp = new WindowState[count + 10];
+ }
+
+ // First remove all existing app windows.
+ i = 0;
+ while (i < count) {
+ final WindowState w = mWindows.get(i);
+ if (w.mAppToken != null) {
+ final WindowState win = mWindows.remove(i);
+ win.mRebuilding = true;
+ mRebuildTmp[numRemoved] = win;
+ mService.mWindowsChanged = true;
+ if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Rebuild removing window: " + win);
+ count--;
+ numRemoved++;
+ continue;
+ } else if (lastBelow == i-1) {
+ if (w.mAttrs.type == TYPE_WALLPAPER) {
+ lastBelow = i;
+ }
+ }
+ i++;
+ }
+
+ // Keep whatever windows were below the app windows still below, by skipping them.
+ lastBelow++;
+ i = lastBelow;
+
+ // First add all of the exiting app tokens... these are no longer in the main app list,
+ // but still have windows shown. We put them in the back because now that the animation is
+ // over we no longer will care about them.
+ final int numStacks = mStacks.size();
+ for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+ AppTokenList exitingAppTokens = mStacks.get(stackNdx).mExitingAppTokens;
+ int NT = exitingAppTokens.size();
+ for (int j = 0; j < NT; j++) {
+ i = exitingAppTokens.get(j).rebuildWindowList(this, i);
+ }
+ }
+
+ // And add in the still active app tokens in Z order.
+ for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+ i = mStacks.get(stackNdx).rebuildWindowList(this, i);
+ }
+
+ i -= lastBelow;
+ if (i != numRemoved) {
+ layoutNeeded = true;
+ Slog.w(TAG_WM, "On display=" + mDisplayId + " Rebuild removed " + numRemoved
+ + " windows but added " + i + " rebuildAppWindowListLocked() "
+ + " callers=" + Debug.getCallers(10));
+ for (i = 0; i < numRemoved; i++) {
+ WindowState ws = mRebuildTmp[i];
+ if (ws.mRebuilding) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new FastPrintWriter(sw, false, 1024);
+ ws.dump(pw, "", true);
+ pw.flush();
+ Slog.w(TAG_WM, "This window was lost: " + ws);
+ Slog.w(TAG_WM, sw.toString());
+ ws.mWinAnimator.destroySurfaceLocked();
+ }
+ }
+ Slog.w(TAG_WM, "Current app token list:");
+ dumpChildrenNames();
+ Slog.w(TAG_WM, "Final window list:");
+ dumpWindows();
+ }
+ Arrays.fill(mRebuildTmp, null);
+ }
+
+ /** Return the list of Windows on this display associated with the input token. */
+ WindowList getTokenWindowsOnDisplay(WindowToken token) {
+ final WindowList windowList = new WindowList();
+ final int count = mWindows.size();
+ for (int i = 0; i < count; i++) {
+ final WindowState win = mWindows.get(i);
+ if (win.mToken == token) {
+ windowList.add(win);
+ }
+ }
+ return windowList;
+ }
+
+ private int addAppWindowExisting(WindowState win, WindowList tokenWindowList) {
+
+ int tokenWindowsPos;
+ // If this application has existing windows, we simply place the new window on top of
+ // them... but keep the starting window on top.
+ if (win.mAttrs.type == TYPE_BASE_APPLICATION) {
+ // Base windows go behind everything else.
+ final WindowState lowestWindow = tokenWindowList.get(0);
+ addWindowToListBefore(win, lowestWindow);
+ tokenWindowsPos = win.mToken.getWindowIndex(lowestWindow);
+ } else {
+ final AppWindowToken atoken = win.mAppToken;
+ final int windowListPos = tokenWindowList.size();
+ final WindowState lastWindow = tokenWindowList.get(windowListPos - 1);
+ if (atoken != null && lastWindow == atoken.startingWindow) {
+ addWindowToListBefore(win, lastWindow);
+ tokenWindowsPos = win.mToken.getWindowIndex(lastWindow);
+ } else {
+ int newIdx = findIdxBasedOnAppTokens(win);
+ // There is a window above this one associated with the same apptoken note that the
+ // window could be a floating window that was created later or a window at the top
+ // of the list of windows associated with this token.
+ if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of "
+ + mWindows.size());
+ mWindows.add(newIdx + 1, win);
+ if (newIdx < 0) {
+ // No window from token found on win's display.
+ tokenWindowsPos = 0;
+ } else {
+ tokenWindowsPos = win.mToken.getWindowIndex(mWindows.get(newIdx)) + 1;
+ }
+ mService.mWindowsChanged = true;
+ }
+ }
+ return tokenWindowsPos;
+ }
+
+ /** Places the first input window after the second input window in the window list. */
+ private void addWindowToListAfter(WindowState first, WindowState second) {
+ final int i = mWindows.indexOf(second);
+ if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "Adding window " + this + " at " + (i + 1) + " of " + mWindows.size()
+ + " (after " + second + ")");
+ mWindows.add(i + 1, first);
+ mService.mWindowsChanged = true;
+ }
+
+ /** Places the first input window before the second input window in the window list. */
+ private void addWindowToListBefore(WindowState first, WindowState second) {
+ int i = mWindows.indexOf(second);
+ if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "Adding window " + this + " at " + i + " of " + mWindows.size()
+ + " (before " + second + ")");
+ if (i < 0) {
+ Slog.w(TAG_WM, "addWindowToListBefore: Unable to find " + second + " in " + mWindows);
+ i = 0;
+ }
+ mWindows.add(i, first);
+ mService.mWindowsChanged = true;
+ }
+
+ /**
+ * This method finds out the index of a window that has the same app token as win. used for z
+ * ordering the windows in mWindows
+ */
+ private int findIdxBasedOnAppTokens(WindowState win) {
+ for(int j = mWindows.size() - 1; j >= 0; j--) {
+ final WindowState wentry = mWindows.get(j);
+ if(wentry.mAppToken == win.mAppToken) {
+ return j;
+ }
+ }
+ return -1;
+ }
+
+ private void dumpChildrenNames() {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new FastPrintWriter(sw, false, 1024);
+ dumpChildrenNames(pw, " ");
+ }
+
+ private void dumpChildrenNames(PrintWriter pw, String prefix) {
+ final String childPrefix = prefix + prefix;
+ for (int j = mStacks.size() - 1; j >= 0; j--) {
+ final TaskStack stack = mStacks.get(j);
+ pw.println("#" + j + " " + getName());
+ stack.dumpChildrenNames(pw, childPrefix);
+ }
+ }
+
+ private void dumpWindows() {
+ final int numDisplays = mService.mDisplayContents.size();
+ for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+ final DisplayContent displayContent = mService.mDisplayContents.valueAt(displayNdx);
+ Slog.v(TAG_WM, " Display #" + displayContent.getDisplayId());
+ final WindowList windows = displayContent.getWindowList();
+ for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
+ Slog.v(TAG_WM, " #" + winNdx + ": " + windows.get(winNdx));
+ }
+ }
+ }
+
+ static final class GetWindowOnDisplaySearchResult {
+ boolean reachedToken;
+ WindowState foundWindow;
+
+ void reset() {
+ reachedToken = false;
+ foundWindow = null;
+ }
+ }
+
+ static final class TaskForResizePointSearchResult {
+ boolean searchDone;
+ Task taskForResize;
+
+ void reset() {
+ searchDone = false;
+ taskForResize = null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index de8e5ac..573eca1 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -153,7 +153,7 @@
// If the bounds are fullscreen, return the value of the fullscreen configuration
if (bounds == null || (bounds.left == 0 && bounds.top == 0
&& bounds.right == di.logicalWidth && bounds.bottom == di.logicalHeight)) {
- return mService.mCurConfiguration.smallestScreenWidthDp;
+ return mService.mGlobalConfiguration.smallestScreenWidthDp;
}
final int baseDisplayWidth = mDisplayContent.mBaseDisplayWidth;
final int baseDisplayHeight = mDisplayContent.mBaseDisplayHeight;
@@ -190,7 +190,7 @@
}
private void initSnapAlgorithmForRotations() {
- final Configuration baseConfig = mService.mCurConfiguration;
+ final Configuration baseConfig = mService.mGlobalConfiguration;
// Initialize the snap algorithms for all 4 screen orientations.
final Configuration config = new Configuration();
@@ -538,15 +538,13 @@
}
final TaskStack fullscreenStack
= mService.mStackIdToStack.get(FULLSCREEN_WORKSPACE_STACK_ID);
- final ArrayList<Task> homeStackTasks = homeStack.getTasks();
- final Task topHomeStackTask = homeStackTasks.get(homeStackTasks.size() - 1);
final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
final boolean homeBehind = (fullscreenStack != null && fullscreenStack.isVisible())
- || (homeStackTasks.size() > 1 && topHomeStackTask != homeTask);
+ || (homeStack.hasMultipleTaskWithHomeTaskNotTop());
// If the home task is an on-top launcher, we don't want to minimize the docked stack.
// Instead we want everything underneath that was visible to remain visible.
// See android.R.attr#onTopLauncher.
- final boolean isOnTopLauncher = topHomeStackTask.isOnTopLauncher();
+ final boolean isOnTopLauncher = homeStack.topTaskIsOnTopLauncher();
setMinimizedDockedStack(homeVisible && !homeBehind && !isOnTopLauncher, animate);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 754a9a6..04f592c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -34,14 +34,13 @@
import android.util.Slog;
import android.view.DisplayInfo;
import android.view.Surface;
-import android.view.animation.Animation;
import android.view.SurfaceControl;
import com.android.server.EventLogTags;
import java.io.PrintWriter;
-class Task implements DimLayer.DimLayerUser {
+class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser {
static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
// Return value from {@link setBounds} indicating no change was made to the Task bounds.
static final int BOUNDS_CHANGE_NONE = 0;
@@ -50,8 +49,8 @@
// Return value from {@link setBounds} indicating the size of the Task bounds changed.
static final int BOUNDS_CHANGE_SIZE = 1 << 1;
+ // TODO: Track parent marks like this in WindowContainer.
TaskStack mStack;
- final AppTokenList mAppTokens = new AppTokenList();
final int mTaskId;
final int mUserId;
boolean mDeferRemoval = false;
@@ -71,8 +70,10 @@
// Whether mBounds is fullscreen
private boolean mFullscreen = true;
- // Contains configurations settings that are different from the global configuration due to
- // stack specific operations. E.g. {@link #setBounds}.
+ /**
+ * Contains configurations settings that are different from the parent configuration due to
+ * stack specific operations. E.g. {@link #setBounds}.
+ */
Configuration mOverrideConfig = Configuration.EMPTY;
// For comparison with DisplayContent bounds.
@@ -93,13 +94,13 @@
private boolean mIsOnTopLauncher;
Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
- Configuration config, boolean isOnTopLauncher) {
+ Configuration overrideConfig, boolean isOnTopLauncher) {
mTaskId = taskId;
mStack = stack;
mUserId = userId;
mService = service;
mIsOnTopLauncher = isOnTopLauncher;
- setBounds(bounds, config);
+ setBounds(bounds, overrideConfig);
}
DisplayContent getDisplayContent() {
@@ -107,18 +108,22 @@
}
void addAppToken(int addPos, AppWindowToken wtoken, int resizeMode, boolean homeTask) {
- final int lastPos = mAppTokens.size();
+ final int lastPos = mChildren.size();
if (addPos >= lastPos) {
addPos = lastPos;
} else {
for (int pos = 0; pos < lastPos && pos < addPos; ++pos) {
- if (mAppTokens.get(pos).removed) {
+ if (mChildren.get(pos).removed) {
// addPos assumes removed tokens are actually gone.
++addPos;
}
}
}
- mAppTokens.add(addPos, wtoken);
+
+ if (wtoken.mParent != null) {
+ wtoken.mParent.removeChild(wtoken);
+ }
+ addChild(wtoken, addPos);
wtoken.mTask = this;
mDeferRemoval = false;
mResizeMode = resizeMode;
@@ -126,15 +131,16 @@
}
private boolean hasWindowsAlive() {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- if (mAppTokens.get(i).hasWindowsAlive()) {
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ if (mChildren.get(i).hasWindowsAlive()) {
return true;
}
}
return false;
}
- void removeLocked() {
+ @Override
+ void removeIfPossible() {
if (hasWindowsAlive() && mStack.isAnimating()) {
if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
mDeferRemoval = true;
@@ -151,6 +157,7 @@
mService.mTaskIdToTask.delete(mTaskId);
}
+ // Change to use reparenting in WC when TaskStack is switched to use WC.
void moveTaskToStack(TaskStack stack, boolean toTop) {
if (stack == mStack) {
return;
@@ -174,50 +181,44 @@
stack.positionTask(this, position, showForAllUsers());
resizeLocked(bounds, config, false /* force */);
- for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
- mAppTokens.get(activityNdx).notifyMovedInStack();
+ for (int activityNdx = mChildren.size() - 1; activityNdx >= 0; --activityNdx) {
+ mChildren.get(activityNdx).notifyMovedInStack();
}
}
- // TODO: Don't forget to switch to WC.detachChild
- void detachChild(AppWindowToken wtoken) {
- if (!removeAppToken(wtoken)) {
- Slog.e(TAG, "detachChild: token=" + this + " not found.");
+ void removeChild(AppWindowToken token) {
+ if (!mChildren.contains(token)) {
+ Slog.e(TAG, "removeChild: token=" + this + " not found.");
+ return;
}
- mStack.mExitingAppTokens.remove(wtoken);
- }
- boolean removeAppToken(AppWindowToken wtoken) {
- boolean removed = mAppTokens.remove(wtoken);
- if (mAppTokens.size() == 0) {
+ super.removeChild(token);
+
+ if (mChildren.isEmpty()) {
EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeAppToken: last token");
if (mDeferRemoval) {
- removeLocked();
+ removeIfPossible();
}
}
- wtoken.mTask = null;
- /* Leave mTaskId for now, it might be useful for debug
- wtoken.mTaskId = -1;
- */
- return removed;
+ token.mTask = null;
}
void setSendingToBottom(boolean toBottom) {
- for (int appTokenNdx = 0; appTokenNdx < mAppTokens.size(); appTokenNdx++) {
- mAppTokens.get(appTokenNdx).sendingToBottom = toBottom;
+ for (int appTokenNdx = 0; appTokenNdx < mChildren.size(); appTokenNdx++) {
+ mChildren.get(appTokenNdx).sendingToBottom = toBottom;
}
}
/** Set the task bounds. Passing in null sets the bounds to fullscreen. */
- private int setBounds(Rect bounds, Configuration config) {
- if (config == null) {
- config = Configuration.EMPTY;
+ private int setBounds(Rect bounds, Configuration overrideConfig) {
+ if (overrideConfig == null) {
+ overrideConfig = Configuration.EMPTY;
}
- if (bounds == null && !Configuration.EMPTY.equals(config)) {
+ if (bounds == null && !Configuration.EMPTY.equals(overrideConfig)) {
throw new IllegalArgumentException("null bounds but non empty configuration: "
- + config);
+ + overrideConfig);
}
- if (bounds != null && Configuration.EMPTY.equals(config)) {
+ if (bounds != null && Configuration.EMPTY.equals(overrideConfig)) {
throw new IllegalArgumentException("non null bounds, but empty configuration");
}
boolean oldFullscreen = mFullscreen;
@@ -254,7 +255,7 @@
if (displayContent != null) {
displayContent.mDimLayerController.updateDimLayer(this);
}
- mOverrideConfig = mFullscreen ? Configuration.EMPTY : config;
+ mOverrideConfig = mFullscreen ? Configuration.EMPTY : overrideConfig;
return boundsChange;
}
@@ -299,12 +300,8 @@
return mHomeTask;
}
- private boolean inCropWindowsResizeMode() {
- return !mHomeTask && !isResizeable() && mResizeMode == RESIZE_MODE_CROP_WINDOWS;
- }
-
- boolean resizeLocked(Rect bounds, Configuration configuration, boolean forced) {
- int boundsChanged = setBounds(bounds, configuration);
+ boolean resizeLocked(Rect bounds, Configuration overrideConfig, boolean forced) {
+ int boundsChanged = setBounds(bounds, overrideConfig);
if (forced) {
boundsChanged |= BOUNDS_CHANGE_SIZE;
}
@@ -325,7 +322,7 @@
*/
void prepareFreezingBounds() {
mPreparedFrozenBounds.set(mBounds);
- mPreparedFrozenMergedConfig.setTo(mService.mCurConfiguration);
+ mPreparedFrozenMergedConfig.setTo(mService.mGlobalConfiguration);
mPreparedFrozenMergedConfig.updateFrom(mOverrideConfig);
}
@@ -358,13 +355,10 @@
/** Return true if the current bound can get outputted to the rest of the system as-is. */
private boolean useCurrentBounds() {
final DisplayContent displayContent = mStack.getDisplayContent();
- if (mFullscreen
+ return mFullscreen
|| !StackId.isTaskResizeableByDockedStack(mStack.mStackId)
|| displayContent == null
- || displayContent.getDockedStackVisibleForUserLocked() != null) {
- return true;
- }
- return false;
+ || displayContent.getDockedStackVisibleForUserLocked() != null;
}
/** Original bounds of the task if applicable, otherwise fullscreen rect. */
@@ -395,8 +389,8 @@
*/
boolean getMaxVisibleBounds(Rect out) {
boolean foundTop = false;
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken token = mAppTokens.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final AppWindowToken token = mChildren.get(i);
// skip hidden (or about to hide) apps
if (token.mIsExiting || token.clientHidden || token.hiddenRequested) {
continue;
@@ -432,8 +426,8 @@
final DisplayContent displayContent = mStack.getDisplayContent();
// It doesn't matter if we in particular are part of the resize, since we couldn't have
// a DimLayer anyway if we weren't visible.
- final boolean dockedResizing = displayContent != null ?
- displayContent.mDividerControllerLocked.isResizing() : false;
+ final boolean dockedResizing = displayContent != null
+ && displayContent.mDividerControllerLocked.isResizing();
if (useCurrentBounds()) {
if (inFreeformWorkspace() && getMaxVisibleBounds(out)) {
return;
@@ -459,10 +453,11 @@
return;
}
- // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
- // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
- // system.
- displayContent.getLogicalDisplayRect(out);
+ // The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
+ // not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
+ if (displayContent != null) {
+ displayContent.getLogicalDisplayRect(out);
+ }
}
void setDragResizing(boolean dragResizing, int dragResizeMode) {
@@ -477,12 +472,6 @@
}
}
- private void resetDragResizingChangeReported() {
- for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
- mAppTokens.get(activityNdx).resetDragResizingChangeReported();
- }
- }
-
boolean isDragResizing() {
return mDragResizing;
}
@@ -491,24 +480,6 @@
return mDragResizeMode;
}
- /**
- * Adds all of the tasks windows to {@link WindowManagerService#mWaitingForDrawn} if drag
- * resizing state of the window has been changed.
- */
- void setWaitingForDrawnIfResizingChanged() {
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- mAppTokens.get(i).setWaitingForDrawnIfResizingChanged();
- }
- }
-
- boolean detachFromDisplay() {
- boolean didSomething = false;
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- didSomething |= mAppTokens.get(i).detachFromDisplay();
- }
- return didSomething;
- }
-
void updateDisplayInfo(final DisplayContent displayContent) {
if (displayContent == null) {
return;
@@ -544,49 +515,23 @@
}
}
- private void onResize() {
- for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
- mAppTokens.get(activityNdx).onResize();
- }
- }
-
- private void onMovedByResize() {
- for (int i = mAppTokens.size() - 1; i >= 0; --i) {
- mAppTokens.get(i).onMovedByResize();
- }
- }
-
- /**
- * Cancels any running app transitions associated with the task.
- */
+ /** Cancels any running app transitions associated with the task. */
void cancelTaskWindowTransition() {
- for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
- mAppTokens.get(activityNdx).mAppAnimator.clearAnimation();
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ mChildren.get(i).mAppAnimator.clearAnimation();
}
}
- /**
- * Cancels any running thumbnail transitions associated with the task.
- */
+ /** Cancels any running thumbnail transitions associated with the task. */
void cancelTaskThumbnailTransition() {
- for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
- mAppTokens.get(activityNdx).mAppAnimator.clearThumbnail();
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ mChildren.get(i).mAppAnimator.clearThumbnail();
}
}
boolean showForAllUsers() {
- final int tokensCount = mAppTokens.size();
- return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
- }
-
- boolean isVisible() {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken appToken = mAppTokens.get(i);
- if (appToken.isVisible()) {
- return true;
- }
- }
- return false;
+ final int tokensCount = mChildren.size();
+ return (tokensCount != 0) && mChildren.get(tokensCount - 1).showForAllUsers;
}
boolean inHomeStack() {
@@ -611,8 +556,8 @@
}
AppWindowToken getTopVisibleAppToken() {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken token = mAppTokens.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final AppWindowToken token = mChildren.get(i);
// skip hidden (or about to hide) apps
if (!token.mIsExiting && !token.clientHidden && !token.hiddenRequested) {
return token;
@@ -641,39 +586,69 @@
return mStack.getDisplayContent().getDisplayInfo();
}
- /**
- * See {@link WindowManagerService#overridePlayingAppAnimationsLw}
- */
- void overridePlayingAppAnimations(Animation a) {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- mAppTokens.get(i).overridePlayingAppAnimations(a);
- }
- }
-
void forceWindowsScaleable(boolean force) {
SurfaceControl.openTransaction();
try {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- mAppTokens.get(i).forceWindowsScaleableInTransaction(force);
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ mChildren.get(i).forceWindowsScaleableInTransaction(force);
}
} finally {
SurfaceControl.closeTransaction();
}
}
- boolean isAnimating() {
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken aToken = mAppTokens.get(i);
- if (aToken.isAnimating()) {
- return true;
+ void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
+ DisplayContent.GetWindowOnDisplaySearchResult result) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final AppWindowToken current = mChildren.get(i);
+ if (current == token) {
+ // We have reach the token we are interested in. End search.
+ result.reachedToken = true;
+ return;
+ }
+
+ // We haven't reached the token yet; if this token is not going to the bottom and
+ // has windows on this display, then it is a candidate for what we are looking for.
+ final WindowList tokenWindowList = dc.getTokenWindowsOnDisplay(current);
+ if (!current.sendingToBottom && tokenWindowList.size() > 0) {
+ result.foundWindow = tokenWindowList.get(0);
}
}
- return false;
+ }
+
+ void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
+ DisplayContent.GetWindowOnDisplaySearchResult result) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final AppWindowToken current = mChildren.get(i);
+ if (!result.reachedToken) {
+ if (current == token) {
+ // We have reached the token we are interested in. Get whichever window occurs
+ // after it that is on the same display.
+ result.reachedToken = true;
+ }
+ continue;
+ }
+
+ final WindowList tokenWindowList = dc.getTokenWindowsOnDisplay(current);
+ if (tokenWindowList.size() > 0) {
+ result.foundWindow = tokenWindowList.get(tokenWindowList.size() - 1);
+ return;
+ }
+ }
+ }
+
+ @Override
+ boolean fillsParent() {
+ return mFullscreen || !StackId.isTaskResizeAllowed(mStack.mStackId);
}
@Override
public String toString() {
- return "{taskId=" + mTaskId + " appTokens=" + mAppTokens + " mdr=" + mDeferRemoval + "}";
+ return "{taskId=" + mTaskId + " appTokens=" + mChildren + " mdr=" + mDeferRemoval + "}";
+ }
+
+ String getName() {
+ return toShortString();
}
@Override
@@ -688,13 +663,13 @@
pw.println(doublePrefix + "mFullscreen=" + mFullscreen);
pw.println(doublePrefix + "mBounds=" + mBounds.toShortString());
pw.println(doublePrefix + "mdr=" + mDeferRemoval);
- pw.println(doublePrefix + "appTokens=" + mAppTokens);
+ pw.println(doublePrefix + "appTokens=" + mChildren);
pw.println(doublePrefix + "mTempInsetBounds=" + mTempInsetBounds.toShortString());
final String triplePrefix = doublePrefix + " ";
- for (int i = mAppTokens.size() - 1; i >= 0; i--) {
- final AppWindowToken wtoken = mAppTokens.get(i);
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final AppWindowToken wtoken = mChildren.get(i);
pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
wtoken.dump(pw, triplePrefix);
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 5c321a1..c097128 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -478,7 +478,7 @@
private int getDimSide(int x) {
if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
|| !mTask.mStack.isFullscreen()
- || mService.mCurConfiguration.orientation != ORIENTATION_LANDSCAPE) {
+ || mService.mGlobalConfiguration.orientation != ORIENTATION_LANDSCAPE) {
return CTRL_NONE;
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 21fc08b..44c772b 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -21,6 +21,9 @@
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.DOCKED_BOTTOM;
@@ -29,6 +32,7 @@
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.RESIZE_STACK;
@@ -36,6 +40,7 @@
import android.app.ActivityManager.StackId;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.graphics.Region;
import android.os.Debug;
import android.os.RemoteException;
import android.util.EventLog;
@@ -43,9 +48,9 @@
import android.util.SparseArray;
import android.view.DisplayInfo;
import android.view.Surface;
-import android.view.SurfaceControl;
import android.view.animation.Animation;
+import android.view.WindowManagerPolicy;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.internal.policy.DockedDividerUtils;
@@ -111,6 +116,7 @@
final AppTokenList mExitingAppTokens = new AppTokenList();
/** Detach this stack from its display when animation completes. */
+ // TODO: maybe tie this to WindowContainer#removeChild some how...
boolean mDeferDetach;
private final Rect mTmpAdjustedBounds = new Rect();
@@ -145,10 +151,6 @@
return mDisplayContent;
}
- ArrayList<Task> getTasks() {
- return mTasks;
- }
-
Task findHomeTask() {
if (mStackId != HOME_STACK_ID) {
return null;
@@ -162,6 +164,14 @@
return null;
}
+ boolean hasMultipleTaskWithHomeTaskNotTop() {
+ return mTasks.size() > 1 && !mTasks.get(mTasks.size() - 1).isHomeTask();
+ }
+
+ boolean topTaskIsOnTopLauncher() {
+ return mTasks.get(mTasks.size() - 1).isOnTopLauncher();
+ }
+
/**
* Set the bounds of the stack and its containing tasks.
* @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
@@ -458,7 +468,7 @@
// Snap the position to a target.
final int rotation = displayInfo.rotation;
- final int orientation = mService.mCurConfiguration.orientation;
+ final int orientation = mService.mGlobalConfiguration.orientation;
mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds);
final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
mService.mContext.getResources(), displayWidth, displayHeight,
@@ -710,7 +720,7 @@
di.logicalWidth,
di.logicalHeight,
dockDividerWidth,
- mService.mCurConfiguration.orientation == ORIENTATION_PORTRAIT,
+ mService.mGlobalConfiguration.orientation == ORIENTATION_PORTRAIT,
mTmpRect2).getMiddleTarget().position;
if (dockOnTopOrLeft) {
@@ -898,7 +908,7 @@
void beginImeAdjustAnimation() {
for (int j = mTasks.size() - 1; j >= 0; j--) {
final Task task = mTasks.get(j);
- if (task.isVisible()) {
+ if (task.hasContentToDisplay()) {
task.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
task.setWaitingForDrawnIfResizingChanged();
}
@@ -1073,6 +1083,15 @@
return mMinimizeAmount != 0f;
}
+ void dumpChildrenNames(PrintWriter pw, String prefix) {
+ final String childPrefix = prefix + prefix;
+ for (int j = mTasks.size() - 1; j >= 0; j--) {
+ final Task task = mTasks.get(j);
+ pw.println("#" + j + " " + getName());
+ task.dumpChildrenNames(pw, childPrefix);
+ }
+ }
+
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "mStackId=" + mStackId);
pw.println(prefix + "mDeferDetach=" + mDeferDetach);
@@ -1142,6 +1161,10 @@
return "{stackId=" + mStackId + " tasks=" + mTasks + "}";
}
+ String getName() {
+ return toShortString();
+ }
+
@Override
public String toShortString() {
return "Stack=" + mStackId;
@@ -1163,7 +1186,7 @@
return DOCKED_INVALID;
}
mDisplayContent.getLogicalDisplayRect(mTmpRect);
- final int orientation = mService.mCurConfiguration.orientation;
+ final int orientation = mService.mGlobalConfiguration.orientation;
return getDockSideUnchecked(bounds, mTmpRect, orientation);
}
@@ -1201,16 +1224,130 @@
for (int i = mTasks.size() - 1; i >= 0; i--) {
final Task task = mTasks.get(i);
- for (int j = task.mAppTokens.size() - 1; j >= 0; j--) {
- if (!task.mAppTokens.get(j).hidden) {
- return true;
- }
+ if (task.isVisible()) {
+ return true;
}
}
return false;
}
+ boolean hasTaskForUser(int userId) {
+ for (int i = mTasks.size() - 1; i >= 0; i--) {
+ final Task task = mTasks.get(i);
+ if (task.mUserId == userId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ int taskIdFromPoint(int x, int y) {
+ getBounds(mTmpRect);
+ if (!mTmpRect.contains(x, y) || isAdjustedForMinimizedDockedStack()) {
+ return -1;
+ }
+
+ for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
+ final Task task = mTasks.get(taskNdx);
+ final WindowState win = task.getTopVisibleAppMainWindow();
+ if (win == null) {
+ continue;
+ }
+ // We need to use the task's dim bounds (which is derived from the visible bounds of its
+ // apps windows) for any touch-related tests. Can't use the task's original bounds
+ // because it might be adjusted to fit the content frame. For example, the presence of
+ // the IME adjusting the windows frames when the app window is the IME target.
+ task.getDimBounds(mTmpRect);
+ if (mTmpRect.contains(x, y)) {
+ return task.mTaskId;
+ }
+ }
+
+ return -1;
+ }
+
+ void findTaskForResizePoint(int x, int y, int delta,
+ DisplayContent.TaskForResizePointSearchResult results) {
+ if (!StackId.isTaskResizeAllowed(mStackId)) {
+ results.searchDone = true;
+ return;
+ }
+
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final Task task = mTasks.get(i);
+ if (task.isFullscreen()) {
+ results.searchDone = true;
+ return;
+ }
+
+ // We need to use the task's dim bounds (which is derived from the visible bounds of
+ // its apps windows) for any touch-related tests. Can't use the task's original
+ // bounds because it might be adjusted to fit the content frame. One example is when
+ // the task is put to top-left quadrant, the actual visible area would not start at
+ // (0,0) after it's adjusted for the status bar.
+ task.getDimBounds(mTmpRect);
+ mTmpRect.inset(-delta, -delta);
+ if (mTmpRect.contains(x, y)) {
+ mTmpRect.inset(delta, delta);
+
+ results.searchDone = true;
+
+ if (!mTmpRect.contains(x, y)) {
+ results.taskForResize = task;
+ return;
+ }
+ // User touched inside the task. No need to look further,
+ // focus transfer will be handled in ACTION_UP.
+ return;
+ }
+ }
+ }
+
+ void setTouchExcludeRegion(Task focusedTask, int delta, Region touchExcludeRegion,
+ Rect contentRect, Rect postExclude) {
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final Task task = mTasks.get(i);
+ AppWindowToken token = task.getTopVisibleAppToken();
+ if (token == null || !token.hasContentToDisplay()) {
+ continue;
+ }
+
+ /**
+ * Exclusion region is the region that TapDetector doesn't care about.
+ * Here we want to remove all non-focused tasks from the exclusion region.
+ * We also remove the outside touch area for resizing for all freeform
+ * tasks (including the focused).
+ *
+ * We save the focused task region once we find it, and add it back at the end.
+ */
+
+ task.getDimBounds(mTmpRect);
+
+ if (task == focusedTask) {
+ // Add the focused task rect back into the exclude region once we are done
+ // processing stacks.
+ postExclude.set(mTmpRect);
+ }
+
+ final boolean isFreeformed = task.inFreeformWorkspace();
+ if (task != focusedTask || isFreeformed) {
+ if (isFreeformed) {
+ // If the task is freeformed, enlarge the area to account for outside
+ // touch area for resize.
+ mTmpRect.inset(-delta, -delta);
+ // Intersect with display content rect. If we have system decor (status bar/
+ // navigation bar), we want to exclude that from the tap detection.
+ // Otherwise, if the app is partially placed under some system button (eg.
+ // Recents, Home), pressing that button would cause a full series of
+ // unwanted transfer focus/resume/pause, before we could go home.
+ mTmpRect.intersect(contentRect);
+ }
+ touchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
+ }
+ }
+ }
+
@Override // AnimatesBounds
public boolean setSize(Rect bounds) {
synchronized (mService.mWindowMap) {
@@ -1300,4 +1437,148 @@
mTasks.get(i).overridePlayingAppAnimations(a);
}
}
+
+ /** Returns true if a removal action is still being deferred. */
+ boolean checkCompleteDeferredRemoval() {
+ if (isAnimating()) {
+ return true;
+ }
+ if (mDeferDetach) {
+ mDisplayContent.detachChild(this);
+ }
+
+ boolean stillDeferringRemoval = false;
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final Task task = mTasks.get(i);
+ stillDeferringRemoval |= task.checkCompleteDeferredRemoval();
+ }
+ return stillDeferringRemoval;
+ }
+
+ void checkAppWindowsReadyToShow(int displayId) {
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final Task task = mTasks.get(i);
+ task.checkAppWindowsReadyToShow(displayId);
+ }
+ }
+
+ void updateAllDrawn(int displayId) {
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final Task task = mTasks.get(i);
+ task.updateAllDrawn(displayId);
+ }
+ }
+
+ void stepAppWindowsAnimation(long currentTime, int displayId) {
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final Task task = mTasks.get(i);
+ task.stepAppWindowsAnimation(currentTime, displayId);
+ }
+
+ // TODO: Why aren't we just using the loop above for this? mAppAnimator.animating isn't set
+ // below but is set in the loop above. See if it really matters...
+ final int exitingCount = mExitingAppTokens.size();
+ for (int i = 0; i < exitingCount; i++) {
+ final AppWindowAnimator appAnimator = mExitingAppTokens.get(i).mAppAnimator;
+ appAnimator.wasAnimating = appAnimator.animating;
+ if (appAnimator.stepAnimationLocked(currentTime, displayId)) {
+ mService.mAnimator.setAnimating(true);
+ mService.mAnimator.mAppWindowAnimating = true;
+ } else if (appAnimator.wasAnimating) {
+ // stopped animating, do one more pass through the layout
+ appAnimator.mAppToken.setAppLayoutChanges(
+ WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
+ "exiting appToken " + appAnimator.mAppToken + " done", displayId);
+ if (DEBUG_ANIM) Slog.v(TAG_WM,
+ "updateWindowsApps...: done animating exiting " + appAnimator.mAppToken);
+ }
+ }
+ }
+
+ void onAppTransitionDone() {
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final Task task = mTasks.get(i);
+ task.onAppTransitionDone();
+ }
+ }
+
+ // TODO: Use WindowContainer.compareTo() once everything is using WindowContainer
+ boolean isFirstGreaterThanSecond(AppWindowToken first, AppWindowToken second) {
+ final Task firstTask = first.mTask;
+ final Task secondTask = second.mTask;
+
+ if (firstTask == secondTask) {
+ return first.compareTo(second) > 0;
+ }
+ return mTasks.indexOf(firstTask) > mTasks.indexOf(secondTask);
+ }
+
+ int rebuildWindowList(DisplayContent dc, int addIndex) {
+ final int count = mTasks.size();
+ for (int i = 0; i < count; i++) {
+ final Task task = mTasks.get(i);
+ addIndex = task.rebuildWindowList(dc, addIndex);
+ }
+ return addIndex;
+ }
+
+ void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
+ DisplayContent.GetWindowOnDisplaySearchResult result) {
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final Task task = mTasks.get(i);
+ task.getWindowOnDisplayBeforeToken(dc, token, result);
+ if (result.reachedToken) {
+ // We have reach the token we are interested in. End search.
+ return;
+ }
+ }
+ }
+
+ void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
+ DisplayContent.GetWindowOnDisplaySearchResult result) {
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final Task task = mTasks.get(i);
+ task.getWindowOnDisplayAfterToken(dc, token, result);
+ if (result.foundWindow != null) {
+ // We have found a window after the token. End search.
+ return;
+ }
+ }
+ }
+
+ // TODO: Remove once switched to use WindowContainer
+ int getOrientation() {
+ if (!StackId.canSpecifyOrientation(mStackId)) {
+ return SCREEN_ORIENTATION_UNSET;
+ }
+
+ int candidate = SCREEN_ORIENTATION_UNSET;
+
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final Task task = mTasks.get(i);
+
+ if (!task.isVisible()) {
+ continue;
+ }
+
+ final int orientation = task.getOrientation();
+ if (orientation == SCREEN_ORIENTATION_BEHIND) {
+ candidate = orientation;
+ continue;
+ }
+
+ if (orientation != SCREEN_ORIENTATION_UNSET) {
+ if (task.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ return orientation;
+ }
+ }
+ }
+
+ return candidate;
+ }
+
+ // TODO: Remove once switched to use WindowContainer
+ boolean fillsParent() {
+ return mFullscreen;
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index 0310b97..dd9ba73 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -64,7 +64,7 @@
case MotionEvent.ACTION_HOVER_MOVE: {
final int x = (int) motionEvent.getX();
final int y = (int) motionEvent.getY();
- final Task task = mDisplayContent.findTaskForControlPoint(x, y);
+ final Task task = mDisplayContent.findTaskForResizePoint(x, y);
int iconType = TYPE_NOT_SPECIFIED;
if (task != null) {
task.getDimBounds(mTmpRect);
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 5d2a52a..6665a93 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -29,7 +29,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEYGUARD;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
@@ -121,6 +120,9 @@
private final AppTokenList mTmpExitingAppTokens = new AppTokenList();
+ /** The window that was previously hiding the Keyguard. */
+ private WindowState mLastShowWinWhenLocked;
+
private String forceHidingToString() {
switch (mForceHiding) {
case KEYGUARD_NOT_SHOWN: return "KEYGUARD_NOT_SHOWN";
@@ -166,59 +168,21 @@
mDisplayContentsAnimators.delete(displayId);
}
- private void updateAppWindowsLocked(int displayId) {
- ArrayList<TaskStack> stacks = mService.getDisplayContentLocked(displayId).getStacks();
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final TaskStack stack = stacks.get(stackNdx);
- final ArrayList<Task> tasks = stack.getTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
- for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
- final AppWindowAnimator appAnimator = tokens.get(tokenNdx).mAppAnimator;
- appAnimator.wasAnimating = appAnimator.animating;
- if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) {
- appAnimator.animating = true;
- setAnimating(true);
- mAppWindowAnimating = true;
- } else if (appAnimator.wasAnimating) {
- // stopped animating, do one more pass through the layout
- setAppLayoutChanges(appAnimator,
- WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
- "appToken " + appAnimator.mAppToken + " done", displayId);
- if (DEBUG_ANIM) Slog.v(TAG,
- "updateWindowsApps...: done animating " + appAnimator.mAppToken);
- }
- }
- }
-
- mTmpExitingAppTokens.clear();
- mTmpExitingAppTokens.addAll(stack.mExitingAppTokens);
-
- final int exitingCount = mTmpExitingAppTokens.size();
- for (int i = 0; i < exitingCount; i++) {
- final AppWindowAnimator appAnimator = mTmpExitingAppTokens.get(i).mAppAnimator;
- // stepAnimation can trigger finishExit->removeWindowInnerLocked
- // ->performSurfacePlacement
- // performSurfacePlacement will directly manipulate the mExitingAppTokens list
- // so we need to iterate over a copy and check for modifications.
- if (!stack.mExitingAppTokens.contains(appAnimator)) {
- continue;
- }
- appAnimator.wasAnimating = appAnimator.animating;
- if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) {
- setAnimating(true);
- mAppWindowAnimating = true;
- } else if (appAnimator.wasAnimating) {
- // stopped animating, do one more pass through the layout
- setAppLayoutChanges(appAnimator,
- WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
- "exiting appToken " + appAnimator.mAppToken + " done", displayId);
- if (DEBUG_ANIM) Slog.v(TAG,
- "updateWindowsApps...: done animating exiting "
- + appAnimator.mAppToken);
- }
- }
+ /**
+ * @return The window that is currently hiding the Keyguard, or if it was hiding the Keyguard,
+ * and it's still animating.
+ */
+ private WindowState getWinShowWhenLockedOrAnimating() {
+ final WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw();
+ if (winShowWhenLocked != null) {
+ return winShowWhenLocked;
}
+ if (mLastShowWinWhenLocked != null && mLastShowWinWhenLocked.isOnScreen()
+ && mLastShowWinWhenLocked.isAnimatingLw()
+ && (mLastShowWinWhenLocked.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0) {
+ return mLastShowWinWhenLocked;
+ }
+ return null;
}
private boolean shouldForceHide(WindowState win) {
@@ -227,18 +191,20 @@
((imeTarget.getAttrs().flags & FLAG_SHOW_WHEN_LOCKED) != 0
|| !mPolicy.canBeForceHidden(imeTarget, imeTarget.mAttrs));
- final WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw();
+ final WindowState winShowWhenLocked = getWinShowWhenLockedOrAnimating();
final AppWindowToken appShowWhenLocked = winShowWhenLocked == null ?
null : winShowWhenLocked.mAppToken;
boolean allowWhenLocked = false;
// Show IME over the keyguard if the target allows it
allowWhenLocked |= (win.mIsImWindow || imeTarget == win) && showImeOverKeyguard;
- // Show SHOW_WHEN_LOCKED windows
- allowWhenLocked |= (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
+ // Show SHOW_WHEN_LOCKED windows that turn on the screen
+ allowWhenLocked |= (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0 && win.mTurnOnScreen;
if (appShowWhenLocked != null) {
allowWhenLocked |= appShowWhenLocked == win.mAppToken
+ // Show all SHOW_WHEN_LOCKED windows if some apps are shown over lockscreen
+ || (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0
// Show error dialogs over apps that are shown on lockscreen
|| (win.mAttrs.privateFlags & PRIVATE_FLAG_SYSTEM_ERROR) != 0;
}
@@ -554,6 +520,11 @@
mPostKeyguardExitAnimation = null;
}
}
+
+ final WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw();
+ if (winShowWhenLocked != null) {
+ mLastShowWinWhenLocked = winShowWhenLocked;
+ }
}
private void updateWallpaperLocked(int displayId) {
@@ -621,54 +592,6 @@
}
}
- /** See if any windows have been drawn, so they (and others associated with them) can now be
- * shown. */
- private void testTokenMayBeDrawnLocked(int displayId) {
- // See if any windows have been drawn, so they (and others
- // associated with them) can now be shown.
- final ArrayList<Task> tasks = mService.getDisplayContentLocked(displayId).getTasks();
- final int numTasks = tasks.size();
- for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
- final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
- final int numTokens = tokens.size();
- for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
- final AppWindowToken wtoken = tokens.get(tokenNdx);
- AppWindowAnimator appAnimator = wtoken.mAppAnimator;
- final boolean allDrawn = wtoken.allDrawn;
- if (allDrawn != appAnimator.allDrawn) {
- appAnimator.allDrawn = allDrawn;
- if (allDrawn) {
- // The token has now changed state to having all
- // windows shown... what to do, what to do?
- if (appAnimator.freezingScreen) {
- appAnimator.showAllWindowsLocked();
- wtoken.stopFreezingScreen(false, true);
- if (DEBUG_ORIENTATION) Slog.i(TAG,
- "Setting mOrientationChangeComplete=true because wtoken "
- + wtoken + " numInteresting=" + wtoken.numInterestingWindows
- + " numDrawn=" + wtoken.numDrawnWindows);
- // This will set mOrientationChangeComplete and cause a pass through
- // layout.
- setAppLayoutChanges(appAnimator,
- WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
- "testTokenMayBeDrawnLocked: freezingScreen", displayId);
- } else {
- setAppLayoutChanges(appAnimator,
- WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM,
- "testTokenMayBeDrawnLocked", displayId);
-
- // We can now show all of the drawn windows!
- if (!mService.mOpeningApps.contains(wtoken)) {
- orAnimating(appAnimator.showAllWindowsLocked());
- }
- }
- }
- }
- }
- }
- }
-
-
/** Locked on mService.mWindowMap. */
private void animateLocked(long frameTimeNs) {
if (!mInitialized) {
@@ -692,7 +615,8 @@
final int numDisplays = mDisplayContentsAnimators.size();
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
- updateAppWindowsLocked(displayId);
+ final DisplayContent displayContent = mService.getDisplayContentLocked(displayId);
+ displayContent.stepAppWindowsAnimation(mCurrentTime);
DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
final ScreenRotationAnimation screenRotationAnimation =
@@ -730,8 +654,9 @@
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
+ final DisplayContent displayContent = mService.getDisplayContentLocked(displayId);
- testTokenMayBeDrawnLocked(displayId);
+ displayContent.checkAppWindowsReadyToShow();
final ScreenRotationAnimation screenRotationAnimation =
mDisplayContentsAnimators.valueAt(i).mScreenRotationAnimation;
@@ -739,12 +664,10 @@
screenRotationAnimation.updateSurfacesInTransaction();
}
- orAnimating(mService.getDisplayContentLocked(displayId).animateDimLayers());
- orAnimating(mService.getDisplayContentLocked(displayId).getDockedDividerController()
- .animate(mCurrentTime));
+ orAnimating(displayContent.animateDimLayers());
+ orAnimating(displayContent.getDockedDividerController().animate(mCurrentTime));
//TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null
- && displayId == Display.DEFAULT_DISPLAY) {
+ if (mService.mAccessibilityController != null && displayContent.isDefaultDisplay) {
mService.mAccessibilityController.drawMagnifiedRegionBorderIfNeededLocked();
}
}
@@ -923,11 +846,6 @@
}
}
- void setAppLayoutChanges(
- AppWindowAnimator appAnimator, int changes, String reason, int displayId) {
- appAnimator.mAppToken.setAppLayoutChanges(changes, reason, displayId);
- }
-
private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) {
DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
if (displayAnimator == null) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d37d0e1..106284a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -17,29 +17,42 @@
package com.android.server.wm;
import android.annotation.CallSuper;
+import android.view.animation.Animation;
+import java.io.PrintWriter;
import java.util.Comparator;
import java.util.LinkedList;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
/**
* Defines common functionality for classes that can hold windows directly or through their
- * children.
+ * children in a hierarchy form.
* The test class is {@link WindowContainerTests} which must be kept up-to-date and ran anytime
* changes are made to this class.
*/
-class WindowContainer {
+class WindowContainer<E extends WindowContainer> implements Comparable<WindowContainer> {
// The parent of this window container.
- private WindowContainer mParent = null;
+ protected WindowContainer mParent = null;
// List of children for this window container. List is in z-order as the children appear on
// screen with the top-most window container at the tail of the list.
- protected final LinkedList<WindowContainer> mChildren = new LinkedList();
+ protected final LinkedList<E> mChildren = new LinkedList();
- protected WindowContainer getParent() {
+ // The specified orientation for this window container.
+ protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+ final protected WindowContainer getParent() {
return mParent;
}
+ // Temp. holders for a chain of containers we are currently processing.
+ private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList();
+ private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList();
+
/**
* Adds the input window container has a child of this container in order based on the input
* comparator.
@@ -48,7 +61,12 @@
* If null, the child will be added to the top.
*/
@CallSuper
- protected void addChild(WindowContainer child, Comparator<WindowContainer> comparator) {
+ protected void addChild(E child, Comparator<E> comparator) {
+ if (child.mParent != null) {
+ throw new IllegalArgumentException("addChild: container=" + child
+ + " is already a child of container=" + child.mParent
+ + " can't add to container=" + this);
+ }
child.mParent = this;
if (mChildren.isEmpty() || comparator == null) {
@@ -67,6 +85,33 @@
mChildren.add(child);
}
+ /** Adds the input window container has a child of this container at the input index. */
+ @CallSuper
+ protected void addChild(E child, int index) {
+ if (child.mParent != null) {
+ throw new IllegalArgumentException("addChild: container=" + child
+ + " is already a child of container=" + child.mParent
+ + " can't add to container=" + this);
+ }
+ child.mParent = this;
+ mChildren.add(index, child);
+ }
+
+ /**
+ * Removes the input child container from this container which is its parent.
+ *
+ * @return True if the container did contain the input child and it was detached.
+ */
+ @CallSuper
+ void removeChild(E child) {
+ if (mChildren.remove(child)) {
+ child.mParent = null;
+ } else {
+ throw new IllegalArgumentException("removeChild: container=" + child
+ + " is not a child of container=" + this);
+ }
+ }
+
/**
* Removes this window container and its children with no regard for what else might be going on
* in the system. For example, the container will be removed during animation if this method is
@@ -85,7 +130,7 @@
}
if (mParent != null) {
- mParent.detachChild(this);
+ mParent.removeChild(this);
}
}
@@ -103,17 +148,6 @@
}
}
- /** Detaches the input child container from this container which is its parent. */
- @CallSuper
- void detachChild(WindowContainer child) {
- if (mChildren.remove(child)) {
- child.mParent = null;
- } else {
- throw new IllegalArgumentException("detachChild: container=" + child
- + " is not a child of container=" + this);
- }
- }
-
/** Returns true if this window container has the input child. */
boolean hasChild(WindowContainer child) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
@@ -193,7 +227,38 @@
}
}
+ /**
+ * Returns true if the container or one of its children as some content it can display or wants
+ * to display (e.g. app views or saved surface).
+ *
+ * NOTE: While this method will return true if the there is some content to display, it doesn't
+ * mean the container is visible. Use {@link #isVisible()} to determine if the container is
+ * visible.
+ */
+ boolean hasContentToDisplay() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mChildren.get(i);
+ if (wc.hasContentToDisplay()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the container or one of its children is considered visible from the
+ * WindowManager perspective which usually means valid surface and some other internal state
+ * are true.
+ *
+ * NOTE: While this method will return true if the surface is visible, it doesn't mean the
+ * client has actually displayed any content. Use {@link #hasContentToDisplay()} to determine if
+ * the container has any content to display.
+ */
boolean isVisible() {
+ // TODO: Will this be more correct if it checks the visibility of its parents?
+ // It depends...For example, Tasks and Stacks are only visible if there children are visible
+ // but, WindowState are not visible if there parent are not visible. Maybe have the
+ // container specify which direction to treverse for for visibility?
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
if (wc.isVisible()) {
@@ -207,4 +272,223 @@
WindowContainer getTop() {
return mChildren.isEmpty() ? this : mChildren.peekLast();
}
+
+ /** Returns true if there is still a removal being deferred */
+ boolean checkCompleteDeferredRemoval() {
+ boolean stillDeferringRemoval = false;
+
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mChildren.get(i);
+ stillDeferringRemoval |= wc.checkCompleteDeferredRemoval();
+ }
+
+ return stillDeferringRemoval;
+ }
+
+ /** Checks if all windows in an app are all drawn and shows them if needed. */
+ // TODO: The displayId shouldn't be needed as there shouldn't be a container on more than one
+ // display. Remove once we migrate DisplayContent to use WindowContainer.
+ void checkAppWindowsReadyToShow(int displayId) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mChildren.get(i);
+ wc.checkAppWindowsReadyToShow(displayId);
+ }
+ }
+
+ /**
+ * Updates the current all drawn status for this container. That is all its children
+ * that should draw something have done so.
+ */
+ // TODO: The displayId shouldn't be needed as there shouldn't be a container on more than one
+ // display. Remove once we migrate DisplayContent to use WindowContainer.
+ void updateAllDrawn(int displayId) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mChildren.get(i);
+ wc.updateAllDrawn(displayId);
+ }
+ }
+
+ /** Step currently ongoing animation for App window containers. */
+ // TODO: The displayId shouldn't be needed as there shouldn't be a container on more than one
+ // display. Remove once we migrate DisplayContent to use WindowContainer.
+ void stepAppWindowsAnimation(long currentTime, int displayId) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mChildren.get(i);
+ wc.stepAppWindowsAnimation(currentTime, displayId);
+ }
+ }
+
+ void onAppTransitionDone() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mChildren.get(i);
+ wc.onAppTransitionDone();
+ }
+ }
+
+ void overridePlayingAppAnimations(Animation a) {
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ mChildren.get(i).overridePlayingAppAnimations(a);
+ }
+ }
+
+ void setOrientation(int orientation) {
+ mOrientation = orientation;
+ }
+
+ /**
+ * Returns the specified orientation for this window container or one of its children is there
+ * is one set, or {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSET} if no
+ * specification is set.
+ * NOTE: {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} is a
+ * specification...
+ */
+ int getOrientation() {
+
+ if (!fillsParent() || !isVisible()) {
+ // Ignore invisible containers or containers that don't completely fills their parents.
+ return SCREEN_ORIENTATION_UNSET;
+ }
+
+ // The container fills its parent so we can use it orientation if it has one specified,
+ // otherwise we prefer to use the orientation of its topmost child that has one
+ // specified and fall back on this container's unset or unspecified value as a candidate
+ // if none of the children have a better candidate for the orientation.
+ if (mOrientation != SCREEN_ORIENTATION_UNSET
+ && mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ return mOrientation;
+ }
+ int candidate = mOrientation;
+
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mChildren.get(i);
+
+ final int orientation = wc.getOrientation();
+ if (orientation == SCREEN_ORIENTATION_BEHIND) {
+ // container wants us to use the orientation of the container behind it. See if we
+ // can find one. Else return SCREEN_ORIENTATION_BEHIND so the caller can choose to
+ // look behind this container.
+ candidate = orientation;
+ continue;
+ }
+
+ if (orientation == SCREEN_ORIENTATION_UNSET) {
+ continue;
+ }
+
+ if (wc.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ // Use the orientation if the container fills its parent or requested an explicit
+ // orientation that isn't SCREEN_ORIENTATION_UNSPECIFIED.
+ return orientation;
+ }
+ }
+
+ return candidate;
+ }
+
+ /**
+ * Returns true if this container is opaque and fills all the space made available by its parent
+ * container.
+ *
+ * NOTE: It is possible for this container to occupy more space than the parent has (or less),
+ * this is just a signal from the client to window manager stating its intent, but not what it
+ * actually does.
+ */
+ boolean fillsParent() {
+ return false;
+ }
+
+ /**
+ * Rebuilds the WindowList for the input display content.
+ * @param dc The display content to rebuild the window list for.
+ * @param addIndex The index in the window list to add the next entry to.
+ * @return The next index in the window list to.
+ */
+ // TODO: Hoping we can get rid of WindowList so this method wouldn't be needed.
+ int rebuildWindowList(DisplayContent dc, int addIndex) {
+ final int count = mChildren.size();
+ for (int i = 0; i < count; i++) {
+ final WindowContainer wc = mChildren.get(i);
+ addIndex = wc.rebuildWindowList(dc, addIndex);
+ }
+ return addIndex;
+ }
+
+ /**
+ * Returns 1, 0, or -1 depending on if this container is greater than, equal to, or lesser than
+ * the input container in terms of z-order.
+ */
+ @Override
+ public int compareTo(WindowContainer other) {
+ if (this == other) {
+ return 0;
+ }
+
+ if (mParent != null && mParent == other.mParent) {
+ final LinkedList<WindowContainer> list = mParent.mChildren;
+ return list.indexOf(this) > list.indexOf(other) ? 1 : -1;
+ }
+
+ final LinkedList<WindowContainer> thisParentChain = mTmpChain1;
+ final LinkedList<WindowContainer> otherParentChain = mTmpChain2;
+ getParents(thisParentChain);
+ other.getParents(otherParentChain);
+
+ // Find the common ancestor of both containers.
+ WindowContainer commonAncestor = null;
+ WindowContainer thisTop = thisParentChain.peekLast();
+ WindowContainer otherTop = otherParentChain.peekLast();
+ while (thisTop != null && otherTop != null && thisTop == otherTop) {
+ commonAncestor = thisParentChain.removeLast();
+ otherParentChain.removeLast();
+ thisTop = thisParentChain.peekLast();
+ otherTop = otherParentChain.peekLast();
+ }
+
+ // Containers don't belong to the same hierarchy???
+ if (commonAncestor == null) {
+ throw new IllegalArgumentException("No in the same hierarchy this="
+ + thisParentChain + " other=" + otherParentChain);
+ }
+
+ // Children are always considered greater than their parents, so if one of the containers
+ // we are comparing it the parent of the other then whichever is the child is greater.
+ if (commonAncestor == this) {
+ return -1;
+ } else if (commonAncestor == other) {
+ return 1;
+ }
+
+ // The position of the first non-common ancestor in the common ancestor list determines
+ // which is greater the which.
+ final LinkedList<WindowContainer> list = commonAncestor.mChildren;
+ return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast())
+ ? 1 : -1;
+ }
+
+ private void getParents(LinkedList<WindowContainer> parents) {
+ parents.clear();
+ WindowContainer current = this;
+ do {
+ parents.addLast(current);
+ current = current.mParent;
+ } while (current != null);
+ }
+
+ /**
+ * Dumps the names of this container children in the input print writer indenting each
+ * level with the input prefix.
+ */
+ void dumpChildrenNames(PrintWriter pw, String prefix) {
+ final String childPrefix = prefix + prefix;
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mChildren.get(i);
+ pw.println("#" + i + " " + getName());
+ wc.dumpChildrenNames(pw, childPrefix);
+ }
+ }
+
+ String getName() {
+ return toString();
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5c681d5..4a54df8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
import android.app.AppOpsManager;
@@ -161,7 +162,6 @@
import java.net.Socket;
import java.text.DateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
@@ -169,7 +169,6 @@
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.StatusBarManager.DISABLE_MASK;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
@@ -215,7 +214,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_ORIENTATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
@@ -496,12 +494,6 @@
Runnable mWaitingForDrawnCallback;
/**
- * Used when rebuilding window list to keep track of windows that have
- * been removed.
- */
- WindowState[] mRebuildTmp = new WindowState[20];
-
- /**
* Stores for each user whether screencapture is disabled
* This array is essentially a cache for all userId for
* {@link android.app.admin.DevicePolicyManager#getScreenCaptureDisabled}
@@ -606,7 +598,11 @@
// State while inside of layoutAndPlaceSurfacesLocked().
boolean mFocusMayChange;
- Configuration mCurConfiguration = new Configuration();
+ /**
+ * Current global configuration information. Contains general settings for the entire system,
+ * corresponds to the configuration of the default display.
+ */
+ Configuration mGlobalConfiguration = new Configuration();
// This is held as long as we have the screen frozen, to give us time to
// perform a rotation animation when turning off shows the lock screen which
@@ -932,6 +928,12 @@
}
};
+ final ArrayList<AppFreezeListener> mAppFreezeListeners = new ArrayList<>();
+
+ interface AppFreezeListener {
+ void onAppFreezeTimeout();
+ }
+
public static WindowManagerService main(final Context context,
final InputManagerService im,
final boolean haveInputMethods, final boolean showBootMsgs,
@@ -1971,7 +1973,7 @@
/**
* Performs some centralized bookkeeping clean-up on the window that is being removed.
* NOTE: Should only be called from {@link WindowState#removeImmediately()}
- * TODO: Maybe better handled with a method {@link WindowContainer#detachChild} if we can
+ * TODO: Maybe better handled with a method {@link WindowContainer#removeChild} if we can
* figure-out a good way to have all parents of a WindowState doing the same thing without
* forgetting to add the wiring when a new parent of WindowState is added.
*/
@@ -2697,9 +2699,10 @@
if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
+ " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+ " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
- Animation a = mAppTransition.loadAnimation(lp, transit, enter, mCurConfiguration.uiMode,
- mCurConfiguration.orientation, frame, displayFrame, insets, surfaceInsets,
- isVoiceInteraction, freeform, atoken.mTask.mTaskId);
+ Animation a = mAppTransition.loadAnimation(lp, transit, enter,
+ mGlobalConfiguration.uiMode, mGlobalConfiguration.orientation, frame,
+ displayFrame, insets, surfaceInsets, isVoiceInteraction, freeform,
+ atoken.mTask.mTaskId);
if (a != null) {
if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken);
final int containingWidth = frame.width();
@@ -2790,7 +2793,7 @@
}
private Task createTaskLocked(int taskId, int stackId, int userId, AppWindowToken atoken,
- Rect bounds, Configuration config, boolean isOnTopLauncher) {
+ Rect bounds, Configuration overrideConfig, boolean isOnTopLauncher) {
if (DEBUG_STACK) Slog.i(TAG_WM, "createTaskLocked: taskId=" + taskId + " stackId=" + stackId
+ " atoken=" + atoken + " bounds=" + bounds);
final TaskStack stack = mStackIdToStack.get(stackId);
@@ -2798,7 +2801,7 @@
throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId);
}
EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId);
- Task task = new Task(taskId, stack, userId, this, bounds, config, isOnTopLauncher);
+ Task task = new Task(taskId, stack, userId, this, bounds, overrideConfig, isOnTopLauncher);
mTaskIdToTask.put(taskId, task);
stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */, atoken.showForAllUsers);
return task;
@@ -2808,9 +2811,9 @@
public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int userId,
int configChanges, boolean voiceInteraction, boolean launchTaskBehind,
- Rect taskBounds, Configuration config, int taskResizeMode, boolean alwaysFocusable,
- boolean homeTask, int targetSdkVersion, int rotationAnimationHint,
- boolean isOnTopLauncher) {
+ Rect taskBounds, Configuration overrideConfig, int taskResizeMode,
+ boolean alwaysFocusable, boolean homeTask, int targetSdkVersion,
+ int rotationAnimationHint, boolean isOnTopLauncher) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addAppToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -2838,10 +2841,10 @@
}
atoken = new AppWindowToken(this, token, voiceInteraction);
atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
- atoken.appFullscreen = fullscreen;
+ atoken.setFillsParent(fullscreen);
atoken.showForAllUsers = showForAllUsers;
atoken.targetSdk = targetSdkVersion;
- atoken.requestedOrientation = requestedOrientation;
+ atoken.setOrientation(requestedOrientation);
atoken.layoutConfigChanges = (configChanges &
(ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
atoken.mLaunchTaskBehind = launchTaskBehind;
@@ -2852,7 +2855,7 @@
Task task = mTaskIdToTask.get(taskId);
if (task == null) {
- task = createTaskLocked(taskId, stackId, userId, atoken, taskBounds, config,
+ task = createTaskLocked(taskId, stackId, userId, atoken, taskBounds, overrideConfig,
isOnTopLauncher);
}
task.addAppToken(addPos, atoken, taskResizeMode, homeTask);
@@ -2865,7 +2868,8 @@
@Override
public void setAppTask(IBinder token, int taskId, int stackId, Rect taskBounds,
- Configuration config, int taskResizeMode, boolean homeTask, boolean isOnTopLauncher) {
+ Configuration overrideConfig, int taskResizeMode, boolean homeTask,
+ boolean isOnTopLauncher) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setAppTask()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -2877,14 +2881,11 @@
Slog.w(TAG_WM, "Attempted to set task id of non-existing app token: " + token);
return;
}
- final Task oldTask = atoken.mTask;
- oldTask.removeAppToken(atoken);
Task newTask = mTaskIdToTask.get(taskId);
if (newTask == null) {
- newTask = createTaskLocked(
- taskId, stackId, oldTask.mUserId, atoken, taskBounds, config,
- isOnTopLauncher);
+ newTask = createTaskLocked(taskId, stackId, atoken.mTask.mUserId, atoken,
+ taskBounds, overrideConfig, isOnTopLauncher);
}
newTask.addAppToken(Integer.MAX_VALUE /* at top */, atoken, taskResizeMode, homeTask);
}
@@ -2946,7 +2947,7 @@
AppWindowToken appShowWhenLocked = winShowWhenLocked == null ?
null : winShowWhenLocked.mAppToken;
if (appShowWhenLocked != null) {
- int req = appShowWhenLocked.requestedOrientation;
+ int req = appShowWhenLocked.getOrientation();
if (req == SCREEN_ORIENTATION_BEHIND) {
req = mLastKeyguardForcedOrientation;
}
@@ -2961,91 +2962,7 @@
}
// Top system windows are not requesting an orientation. Start searching from apps.
- return getAppSpecifiedOrientation();
- }
-
- private int getAppSpecifiedOrientation() {
- int lastOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
- boolean findingBehind = false;
- boolean lastFullscreen = false;
- DisplayContent displayContent = getDefaultDisplayContentLocked();
- final ArrayList<Task> tasks = displayContent.getTasks();
- final boolean inMultiWindow = isStackVisibleLocked(DOCKED_STACK_ID)
- || isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID);
- final boolean dockMinimized =
- getDefaultDisplayContentLocked().mDividerControllerLocked.isMinimizedDock();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
- final int firstToken = tokens.size() - 1;
- for (int tokenNdx = firstToken; tokenNdx >= 0; --tokenNdx) {
- final AppWindowToken atoken = tokens.get(tokenNdx);
-
- if (DEBUG_APP_ORIENTATION) Slog.v(TAG_WM, "Checking app orientation: " + atoken);
-
- // if we're about to tear down this window and not seek for
- // the behind activity, don't use it for orientation
- if (!findingBehind && !atoken.hidden && atoken.hiddenRequested) {
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "Skipping " + atoken + " -- going to hide");
- continue;
- }
-
- if (tokenNdx == firstToken) {
- // If we have hit a new Task, and the bottom of the previous group didn't
- // explicitly say to use the orientation behind it, and the last app was
- // full screen, then we'll stick with the user's orientation.
- if (lastOrientation != SCREEN_ORIENTATION_BEHIND && lastFullscreen) {
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Done at " + atoken
- + " -- end of group, return " + lastOrientation);
- return lastOrientation;
- }
- }
-
- // We ignore any hidden applications on the top.
- if (atoken.hiddenRequested) {
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "Skipping " + atoken + " -- hidden on top");
- continue;
- }
-
- // No app except the home app may specify the screen orientation in multi-window,
- // and only if the docked stack is minimized to avoid weirdness when home task
- // temporarily gets moved to the front.
- if (inMultiWindow && (!atoken.mTask.isHomeTask() || !dockMinimized)) {
- continue;
- }
-
- if (tokenNdx == 0) {
- // Last token in this task.
- lastOrientation = atoken.requestedOrientation;
- }
-
- int or = atoken.requestedOrientation;
- // If this application is fullscreen, and didn't explicitly say
- // to use the orientation behind it, then just take whatever
- // orientation it has and ignores whatever is under it.
- lastFullscreen = atoken.appFullscreen;
- if (lastFullscreen && or != SCREEN_ORIENTATION_BEHIND) {
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "Done at " + atoken + " -- full screen, return " + or);
- return or;
- }
- // If this application has requested an explicit orientation, then use it.
- if (or != SCREEN_ORIENTATION_UNSPECIFIED && or != SCREEN_ORIENTATION_BEHIND) {
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "Done at " + atoken + " -- explicitly set, return " + or);
- return or;
- }
- findingBehind |= (or == SCREEN_ORIENTATION_BEHIND);
- }
- }
- if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
- "No app is requesting an orientation, return " + mLastOrientation);
- // The next app has not been requested to be visible, so we keep the current orientation
- // to prevent freezing/unfreezing the display too early unless we are in multi-window, in
- // which we don't let the app customize the orientation unless it was the home task that
- // is handled above.
- return inMultiWindow ? SCREEN_ORIENTATION_UNSPECIFIED : mLastOrientation;
+ return getDefaultDisplayContentLocked().getOrientation();
}
@Override
@@ -3157,12 +3074,12 @@
mWaitingForConfig = false;
mLastFinishedFreezeSource = "new-config";
}
- boolean configChanged = mCurConfiguration.diff(config) != 0;
+ boolean configChanged = mGlobalConfiguration.diff(config) != 0;
if (!configChanged) {
return null;
}
prepareFreezingAllTaskBounds();
- mCurConfiguration = new Configuration(config);
+ mGlobalConfiguration = new Configuration(config);
return onConfigurationChanged();
}
}
@@ -3221,13 +3138,13 @@
}
synchronized(mWindowMap) {
- AppWindowToken atoken = findAppWindowToken(token.asBinder());
+ final AppWindowToken atoken = findAppWindowToken(token.asBinder());
if (atoken == null) {
Slog.w(TAG_WM, "Attempted to set orientation of non-existing app token: " + token);
return;
}
- atoken.requestedOrientation = requestedOrientation;
+ atoken.setOrientation(requestedOrientation);
}
}
@@ -3239,7 +3156,7 @@
return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
- return wtoken.requestedOrientation;
+ return wtoken.getOrientation();
}
}
@@ -3555,9 +3472,9 @@
public void setAppFullscreen(IBinder token, boolean toOpaque) {
synchronized (mWindowMap) {
- AppWindowToken atoken = findAppWindowToken(token);
+ final AppWindowToken atoken = findAppWindowToken(token);
if (atoken != null) {
- atoken.appFullscreen = toOpaque;
+ atoken.setFillsParent(toOpaque);
setWindowOpaqueLocked(token, toOpaque);
mWindowPlacerLocked.requestTraversal();
}
@@ -3721,8 +3638,7 @@
}
if (mAppTransition.getAppTransition() == AppTransition.TRANSIT_TASK_OPEN_BEHIND) {
// We're launchingBehind, add the launching activity to mOpeningApps.
- final WindowState win =
- findFocusedWindowLocked(getDefaultDisplayContentLocked());
+ final WindowState win = getDefaultDisplayContentLocked().findFocusedWindow();
if (win != null) {
final AppWindowToken focusedToken = win.mAppToken;
if (focusedToken != null) {
@@ -3889,42 +3805,12 @@
mH.sendMessage(m);
}
- void dumpAppTokensLocked() {
- final int numStacks = mStackIdToStack.size();
- for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- final TaskStack stack = mStackIdToStack.valueAt(stackNdx);
- Slog.v(TAG_WM, " Stack #" + stack.mStackId + " tasks from bottom to top:");
- final ArrayList<Task> tasks = stack.getTasks();
- final int numTasks = tasks.size();
- for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
- final Task task = tasks.get(taskNdx);
- Slog.v(TAG_WM, " Task #" + task.mTaskId + " activities from bottom to top:");
- AppTokenList tokens = task.mAppTokens;
- final int numTokens = tokens.size();
- for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
- Slog.v(TAG_WM, " activity #" + tokenNdx + ": " + tokens.get(tokenNdx).token);
- }
- }
- }
- }
-
- void dumpWindowsLocked() {
- final int numDisplays = mDisplayContents.size();
- for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
- final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
- Slog.v(TAG_WM, " Display #" + displayContent.getDisplayId());
- final WindowList windows = displayContent.getWindowList();
- for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
- Slog.v(TAG_WM, " #" + winNdx + ": " + windows.get(winNdx));
- }
- }
- }
-
- void moveStackWindowsLocked(DisplayContent displayContent) {
+ /** Rebuilds the input display's window list and does a relayout if something changed. */
+ private void rebuildAppWindowsAndLayoutIfNeededLocked(DisplayContent displayContent) {
final WindowList windows = displayContent.getWindowList();
mTmpWindows.addAll(windows);
- rebuildAppWindowListLocked(displayContent);
+ displayContent.rebuildAppWindowList();
// Set displayContent.layoutNeeded if window order changed.
final int tmpSize = mTmpWindows.size();
@@ -3962,7 +3848,6 @@
mInputMonitor.setUpdateInputWindowsNeededLw();
mWindowPlacerLocked.performSurfacePlacement();
mInputMonitor.updateInputWindowsLw(false /*force*/);
- //dump();
}
public void moveTaskToTop(int taskId) {
@@ -3989,7 +3874,7 @@
if (mAppTransition.isTransitionSet()) {
task.setSendingToBottom(false);
}
- moveStackWindowsLocked(displayContent);
+ rebuildAppWindowsAndLayoutIfNeededLocked(displayContent);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -4011,7 +3896,7 @@
if (mAppTransition.isTransitionSet()) {
task.setSendingToBottom(true);
}
- moveStackWindowsLocked(stack.getDisplayContent());
+ rebuildAppWindowsAndLayoutIfNeededLocked(stack.getDisplayContent());
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -4088,20 +3973,9 @@
return null;
}
- void detachStackLocked(DisplayContent displayContent, TaskStack stack) {
- displayContent.detachStack(stack);
- if (stack.detachFromDisplay()) {
- mWindowPlacerLocked.requestTraversal();
- }
- if (stack.mStackId == DOCKED_STACK_ID) {
- getDefaultDisplayContentLocked().mDividerControllerLocked
- .notifyDockedStackExistsChanged(false);
- }
- }
-
public void detachStack(int stackId) {
synchronized (mWindowMap) {
- TaskStack stack = mStackIdToStack.get(stackId);
+ final TaskStack stack = mStackIdToStack.get(stackId);
if (stack != null) {
final DisplayContent displayContent = stack.getDisplayContent();
if (displayContent != null) {
@@ -4109,7 +3983,7 @@
stack.mDeferDetach = true;
return;
}
- detachStackLocked(displayContent, stack);
+ displayContent.detachChild(stack);
}
}
}
@@ -4128,7 +4002,7 @@
if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + taskId);
return;
}
- task.removeLocked();
+ task.removeIfPossible();
}
}
@@ -4284,7 +4158,7 @@
* Returns a {@link Configuration} object that contains configurations settings
* that should be overridden due to the operation.
*/
- public void resizeTask(int taskId, Rect bounds, Configuration configuration,
+ public void resizeTask(int taskId, Rect bounds, Configuration overrideConfig,
boolean relayout, boolean forced) {
synchronized (mWindowMap) {
Task task = mTaskIdToTask.get(taskId);
@@ -4293,7 +4167,7 @@
+ " not found.");
}
- if (task.resizeLocked(bounds, configuration, forced) && relayout) {
+ if (task.resizeLocked(bounds, overrideConfig, forced) && relayout) {
task.getDisplayContent().layoutNeeded = true;
mWindowPlacerLocked.performSurfacePlacement();
}
@@ -4777,7 +4651,6 @@
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
displayContent.switchUserStacks();
- rebuildAppWindowListLocked(displayContent);
}
mWindowPlacerLocked.performSurfacePlacement();
@@ -4797,22 +4670,13 @@
}
}
- /**
- * Returns whether there is a docked task for the current user.
- */
+ /** Returns whether there is a docked task for the current user. */
boolean hasDockedTasksForUser(int userId) {
final TaskStack stack = mStackIdToStack.get(DOCKED_STACK_ID);
if (stack == null) {
return false;
}
-
- final ArrayList<Task> tasks = stack.getTasks();
- boolean hasUserTask = false;
- for (int i = tasks.size() - 1; i >= 0 && !hasUserTask; i--) {
- final Task task = tasks.get(i);
- hasUserTask = (task.mUserId == userId);
- }
- return hasUserTask;
+ return stack.hasTaskForUser(userId);
}
/* Called by WindowState */
@@ -5829,7 +5693,7 @@
// the top of the method, the caller is obligated to call computeNewConfigurationLocked().
// By updating the Display info here it will be available to
// computeScreenConfigurationLocked later.
- updateDisplayAndOrientationLocked(mCurConfiguration.uiMode);
+ updateDisplayAndOrientationLocked(mGlobalConfiguration.uiMode);
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
if (!inTransaction) {
@@ -6379,9 +6243,9 @@
return null;
}
- /*
- * Instruct the Activity Manager to fetch the current configuration and broadcast
- * that to config-changed listeners if appropriate.
+ /**
+ * Instruct the Activity Manager to fetch new configurations, update global configuration
+ * and broadcast changes to config-changed listeners if appropriate.
*/
void sendNewConfiguration() {
try {
@@ -6698,7 +6562,7 @@
private void handleTapOutsideTask(DisplayContent displayContent, int x, int y) {
int taskId = -1;
synchronized (mWindowMap) {
- final Task task = displayContent.findTaskForControlPoint(x, y);
+ final Task task = displayContent.findTaskForResizePoint(x, y);
if (task != null) {
if (!startPositioningLocked(
task.getTopVisibleAppMainWindow(), true /*resize*/, x, y)) {
@@ -7504,20 +7368,8 @@
synchronized (mWindowMap) {
Slog.w(TAG_WM, "App freeze timeout expired.");
mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
- final int numStacks = mStackIdToStack.size();
- for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- final TaskStack stack = mStackIdToStack.valueAt(stackNdx);
- final ArrayList<Task> tasks = stack.getTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
- for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
- AppWindowToken tok = tokens.get(tokenNdx);
- if (tok.mAppAnimator.freezingScreen) {
- Slog.w(TAG_WM, "Force clearing freeze: " + tok);
- tok.stopFreezingScreen(true, true);
- }
- }
- }
+ for (int i = mAppFreezeListeners.size() - 1; i >=0 ; --i) {
+ mAppFreezeListeners.get(i).onAppFreezeTimeout();
}
}
break;
@@ -8119,7 +7971,7 @@
}
@Override
- public void setForcedDisplayDensity(int displayId, int density) {
+ public void setForcedDisplayDensityForUser(int displayId, int density, int userId) {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
PackageManager.PERMISSION_GRANTED) {
@@ -8129,16 +7981,20 @@
if (displayId != Display.DEFAULT_DISPLAY) {
throw new IllegalArgumentException("Can only set the default display");
}
+
+ final int targetUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "setForcedDisplayDensityForUser",
+ null);
final long ident = Binder.clearCallingIdentity();
try {
synchronized(mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent != null) {
+ if (displayContent != null && mCurrentUserId == targetUserId) {
setForcedDisplayDensityLocked(displayContent, density);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.DISPLAY_DENSITY_FORCED,
- Integer.toString(density), mCurrentUserId);
}
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.DISPLAY_DENSITY_FORCED,
+ Integer.toString(density), targetUserId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -8146,7 +8002,7 @@
}
@Override
- public void clearForcedDisplayDensity(int displayId) {
+ public void clearForcedDisplayDensityForUser(int displayId, int userId) {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
PackageManager.PERMISSION_GRANTED) {
@@ -8156,16 +8012,20 @@
if (displayId != Display.DEFAULT_DISPLAY) {
throw new IllegalArgumentException("Can only set the default display");
}
+
+ final int callingUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "clearForcedDisplayDensityForUser",
+ null);
final long ident = Binder.clearCallingIdentity();
try {
synchronized(mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent != null) {
+ if (displayContent != null && mCurrentUserId == callingUserId) {
setForcedDisplayDensityLocked(displayContent,
displayContent.mInitialDisplayDensity);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.DISPLAY_DENSITY_FORCED, "", mCurrentUserId);
}
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.DISPLAY_DENSITY_FORCED, "", callingUserId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -8215,9 +8075,9 @@
boolean configChanged = updateOrientationFromAppTokensLocked(false);
mTempConfiguration.setToDefaults();
- mTempConfiguration.updateFrom(mCurConfiguration);
+ mTempConfiguration.updateFrom(mGlobalConfiguration);
computeScreenConfigurationLocked(mTempConfiguration);
- configChanged |= mCurConfiguration.diff(mTempConfiguration) != 0;
+ configChanged |= mGlobalConfiguration.diff(mTempConfiguration) != 0;
if (configChanged) {
mWaitingForConfig = true;
@@ -8313,102 +8173,6 @@
return win;
}
- final void rebuildAppWindowListLocked() {
- rebuildAppWindowListLocked(getDefaultDisplayContentLocked());
- }
-
- private void rebuildAppWindowListLocked(final DisplayContent displayContent) {
- final WindowList windows = displayContent.getWindowList();
- int NW = windows.size();
- int i;
- int lastBelow = -1;
- int numRemoved = 0;
-
- if (mRebuildTmp.length < NW) {
- mRebuildTmp = new WindowState[NW+10];
- }
-
- // First remove all existing app windows.
- i=0;
- while (i < NW) {
- WindowState w = windows.get(i);
- if (w.mAppToken != null) {
- WindowState win = windows.remove(i);
- win.mRebuilding = true;
- mRebuildTmp[numRemoved] = win;
- mWindowsChanged = true;
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Rebuild removing window: " + win);
- NW--;
- numRemoved++;
- continue;
- } else if (lastBelow == i-1) {
- if (w.mAttrs.type == TYPE_WALLPAPER) {
- lastBelow = i;
- }
- }
- i++;
- }
-
- // Keep whatever windows were below the app windows still below, by skipping them.
- lastBelow++;
- i = lastBelow;
-
- // First add all of the exiting app tokens... these are no longer in the main app list,
- // but still have windows shown. We put them in the back because now that the animation is
- // over we no longer will care about them.
- final ArrayList<TaskStack> stacks = displayContent.getStacks();
- final int numStacks = stacks.size();
- for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- AppTokenList exitingAppTokens = stacks.get(stackNdx).mExitingAppTokens;
- int NT = exitingAppTokens.size();
- for (int j = 0; j < NT; j++) {
- i = exitingAppTokens.get(j).reAddAppWindows(displayContent, i);
- }
- }
-
- // And add in the still active app tokens in Z order.
- for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- final ArrayList<Task> tasks = stacks.get(stackNdx).getTasks();
- final int numTasks = tasks.size();
- for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
- final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
- final int numTokens = tokens.size();
- for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
- final AppWindowToken wtoken = tokens.get(tokenNdx);
- if (wtoken.mIsExiting && !wtoken.waitingForReplacement()) {
- continue;
- }
- i = wtoken.reAddAppWindows(displayContent, i);
- }
- }
- }
-
- i -= lastBelow;
- if (i != numRemoved) {
- displayContent.layoutNeeded = true;
- Slog.w(TAG_WM, "On display=" + displayContent.getDisplayId() + " Rebuild removed "
- + numRemoved + " windows but added " + i + " rebuildAppWindowListLocked() "
- + " callers=" + Debug.getCallers(10));
- for (i = 0; i < numRemoved; i++) {
- WindowState ws = mRebuildTmp[i];
- if (ws.mRebuilding) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new FastPrintWriter(sw, false, 1024);
- ws.dump(pw, "", true);
- pw.flush();
- Slog.w(TAG_WM, "This window was lost: " + ws);
- Slog.w(TAG_WM, sw.toString());
- ws.mWinAnimator.destroySurfaceLocked();
- }
- }
- Slog.w(TAG_WM, "Current app token list:");
- dumpAppTokensLocked();
- Slog.w(TAG_WM, "Final window list:");
- dumpWindowsLocked();
- }
- Arrays.fill(mRebuildTmp, null);
- }
-
void makeWindowFreezingScreenIfNeededLocked(WindowState w) {
// If the screen is currently frozen or off, then keep
// it frozen/off until this window draws at its new
@@ -8445,27 +8209,15 @@
mWallpaperControllerLocked.hideDeferredWallpapersIfNeeded();
- // Restore window app tokens to the ActivityManager views
- ArrayList<TaskStack> stacks = getDefaultDisplayContentLocked().getStacks();
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ArrayList<Task> tasks = stacks.get(stackNdx).getTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
- for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
- tokens.get(tokenNdx).sendingToBottom = false;
- }
- }
- }
- rebuildAppWindowListLocked();
+ getDefaultDisplayContentLocked().onAppTransitionDone();
changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT;
if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG_WM,
"Wallpaper layer changed: assigning layers + relayout");
moveInputMethodWindowsIfNeededLocked(true);
mWindowPlacerLocked.mWallpaperMayChange = true;
- // Since the window list has been rebuilt, focus might
- // have to be recomputed since the actual order of windows
- // might have changed again.
+ // Since the window list has been rebuilt, focus might have to be recomputed since the
+ // actual order of windows might have changed again.
mFocusMayChange = true;
return changes;
@@ -8475,30 +8227,25 @@
final WindowStateAnimator winAnimator = w.mWinAnimator;
if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq && !w.isGoneForLayoutLw()) {
final Task task = w.getTask();
- // In the case of stack bound animations, the window frames
- // will update (unlike other animations which just modifiy
- // various transformation properties). We don't want to
- // notify the client of frame changes in this case. Not only
- // is it a lot of churn, but the frame may not correspond
- // to the surface size or the onscreen area at various
- // phases in the animation, and the client will become
- // sad and confused.
+ // In the case of stack bound animations, the window frames will update (unlike other
+ // animations which just modify various transformation properties). We don't want to
+ // notify the client of frame changes in this case. Not only is it a lot of churn, but
+ // the frame may not correspond to the surface size or the onscreen area at various
+ // phases in the animation, and the client will become sad and confused.
if (task != null && task.mStack.getBoundsAnimating()) {
return;
}
w.setReportResizeHints();
boolean configChanged = w.isConfigChanged();
if (DEBUG_CONFIGURATION && configChanged) {
- Slog.v(TAG_WM, "Win " + w + " config changed: "
- + mCurConfiguration);
+ Slog.v(TAG_WM, "Win " + w + " config changed: " + mGlobalConfiguration);
}
final boolean dragResizingChanged = w.isDragResizeChanged()
&& !w.isDragResizingChangeReported();
- if (localLOGV) Slog.v(TAG_WM, "Resizing " + w
- + ": configChanged=" + configChanged
- + " dragResizingChanged=" + dragResizingChanged
- + " last=" + w.mLastFrame + " frame=" + w.mFrame);
+ if (localLOGV) Slog.v(TAG_WM, "Resizing " + w + ": configChanged=" + configChanged
+ + " dragResizingChanged=" + dragResizingChanged + " last=" + w.mLastFrame
+ + " frame=" + w.mFrame);
// We update mLastFrame always rather than in the conditional with the
// last inset variables, because mFrameSizeChanged only tracks the
@@ -8848,7 +8595,7 @@
final int displayCount = mDisplayContents.size();
for (int i = 0; i < displayCount; i++) {
final DisplayContent displayContent = mDisplayContents.valueAt(i);
- WindowState win = findFocusedWindowLocked(displayContent);
+ final WindowState win = displayContent.findFocusedWindow();
if (win != null) {
return win;
}
@@ -8856,67 +8603,6 @@
return null;
}
- WindowState findFocusedWindowLocked(DisplayContent displayContent) {
- final WindowList windows = displayContent.getWindowList();
- for (int i = windows.size() - 1; i >= 0; i--) {
- final WindowState win = windows.get(i);
-
- if (localLOGV || DEBUG_FOCUS) Slog.v(
- TAG_WM, "Looking for focus: " + i
- + " = " + win
- + ", flags=" + win.mAttrs.flags
- + ", canReceive=" + win.canReceiveKeys());
-
- if (!win.canReceiveKeys()) {
- continue;
- }
-
- AppWindowToken wtoken = win.mAppToken;
-
- // If this window's application has been removed, just skip it.
- if (wtoken != null && (wtoken.removed || wtoken.sendingToBottom)) {
- if (DEBUG_FOCUS) Slog.v(TAG_WM, "Skipping " + wtoken + " because "
- + (wtoken.removed ? "removed" : "sendingToBottom"));
- continue;
- }
-
- // Descend through all of the app tokens and find the first that either matches
- // win.mAppToken (return win) or mFocusedApp (return null).
- if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING &&
- mFocusedApp != null) {
- ArrayList<Task> tasks = displayContent.getTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
- int tokenNdx = tokens.size() - 1;
- for ( ; tokenNdx >= 0; --tokenNdx) {
- final AppWindowToken token = tokens.get(tokenNdx);
- if (wtoken == token) {
- break;
- }
- if (mFocusedApp == token && token.windowsAreFocusable()) {
- // Whoops, we are below the focused app whose windows are focusable...
- // No focus for you!!!
- if (localLOGV || DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM,
- "findFocusedWindow: Reached focused app=" + mFocusedApp);
- return null;
- }
- }
- if (tokenNdx >= 0) {
- // Early exit from loop, must have found the matching token.
- break;
- }
- }
- }
-
- if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Found new focus @ " + i +
- " = " + win);
- return win;
- }
-
- if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows.");
- return null;
- }
-
void startFreezingDisplayLocked(boolean inTransaction, int exitAnim, int enterAnim) {
if (mDisplayFrozen) {
return;
@@ -9559,7 +9245,7 @@
}
}
pw.println();
- pw.print(" mCurConfiguration="); pw.println(this.mCurConfiguration);
+ pw.print(" mGlobalConfiguration="); pw.println(mGlobalConfiguration);
pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad);
pw.print(" mCurrentFocus="); pw.println(mCurrentFocus);
if (mLastFocus != mCurrentFocus) {
@@ -10039,7 +9725,7 @@
public void setWillReplaceWindow(IBinder token, boolean animate) {
synchronized (mWindowMap) {
final AppWindowToken appWindowToken = findAppWindowToken(token);
- if (appWindowToken == null || !appWindowToken.isVisible()) {
+ if (appWindowToken == null || !appWindowToken.hasContentToDisplay()) {
Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
+ token);
return;
@@ -10063,7 +9749,7 @@
public void setWillReplaceWindows(IBinder token, boolean childrenOnly) {
synchronized (mWindowMap) {
final AppWindowToken appWindowToken = findAppWindowToken(token);
- if (appWindowToken == null || !appWindowToken.isVisible()) {
+ if (appWindowToken == null || !appWindowToken.hasContentToDisplay()) {
Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
+ token);
return;
@@ -10614,4 +10300,15 @@
}
}
}
+
+ void registerAppFreezeListener(AppFreezeListener listener) {
+ if (!mAppFreezeListeners.contains(listener)) {
+ mAppFreezeListeners.add(listener);
+ }
+ }
+
+ void unregisterAppFreezeListener(AppFreezeListener listener) {
+ mAppFreezeListeners.remove(listener);
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index fd47f5b..bbb1fbb 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -141,10 +141,8 @@
}
}
-/**
- * A window in the window manager.
- */
-class WindowState extends WindowContainer implements WindowManagerPolicy.WindowState {
+/** A window in the window manager. */
+class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM;
// The minimal size of a window within the usable area of the freeform stack.
@@ -551,9 +549,9 @@
* Compares to window sub-layers and returns -1 if the first is lesser than the second in terms
* of z-order and 1 otherwise.
*/
- private static final Comparator<WindowContainer> sWindowSubLayerComparator = (w1, w2) -> {
- final int layer1 = ((WindowState)w1).mSubLayer;
- final int layer2 = ((WindowState)w2).mSubLayer;
+ private static final Comparator<WindowState> sWindowSubLayerComparator = (w1, w2) -> {
+ final int layer1 = w1.mSubLayer;
+ final int layer2 = w2.mSubLayer;
if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
// We insert the child window into the list ordered by the sub-layer.
// For same sub-layers, the negative one should go below others; the positive one should
@@ -1181,10 +1179,8 @@
}
}
- // TODO: Sigh...another is visible method...tried to consolidate with other isVisible methods
- // below, but failed. Need to figure-out a good way to handle this long term...
@Override
- boolean isVisible() {
+ boolean hasContentToDisplay() {
// If we're animating with a saved surface, we're already visible.
// Return true so that the alpha doesn't get cleared.
if (!mAppFreezing && isDrawnLw()
@@ -1193,7 +1189,18 @@
return true;
}
- return super.isVisible();
+ return super.hasContentToDisplay();
+ }
+
+ @Override
+ boolean isVisible() {
+ if ((mAppToken == null || !mAppToken.hiddenRequested) && isVisibleUnchecked()) {
+ // Is this window visible? It is not visible if there is no surface, or we are in the
+ // process of running an exit animation that will remove the surface, or its app token
+ // has been hidden.
+ return true;
+ }
+ return false;
}
/**
@@ -1206,13 +1213,9 @@
&& !mAnimatingExit && !mDestroying && (!mIsWallpaper || mWallpaperVisible);
}
- /**
- * Is this window visible? It is not visible if there is no surface, or we are in the process
- * of running an exit animation that will remove the surface, or its app token has been hidden.
- */
@Override
public boolean isVisibleLw() {
- return (mAppToken == null || !mAppToken.hiddenRequested) && isVisibleUnchecked();
+ return isVisible();
}
/**
@@ -1238,7 +1241,8 @@
* Is this window visible, ignoring its app token? It is not visible if there is no surface,
* or we are in the process of running an exit animation that will remove the surface.
*/
- public boolean isWinVisibleLw() {
+ // TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
+ boolean isWinVisibleLw() {
return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.mAppAnimator.animating)
&& isVisibleUnchecked();
}
@@ -1287,7 +1291,7 @@
* Like isOnScreen(), but ignores any force hiding of the window due
* to the keyguard.
*/
- boolean isOnScreenIgnoringKeyguard() {
+ private boolean isOnScreenIgnoringKeyguard() {
if (!mHasSurface || mDestroying) {
return false;
}
@@ -1443,7 +1447,7 @@
boolean changed = false;
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
changed |= c.onAppVisibilityChanged(visible, runningAppAnimation);
}
@@ -1494,7 +1498,7 @@
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
changed |= c.onSetAppExiting();
}
@@ -1537,7 +1541,7 @@
void onUnfreezeBounds() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.onUnfreezeBounds();
}
@@ -1582,7 +1586,7 @@
boolean isConfigChanged() {
getMergedConfig(mTmpConfig);
- // If the merged configuration is still empty, it means that we haven't issues the
+ // If the merged configuration is still empty, it means that we haven't issued the
// configuration to the client yet and we need to return true so the configuration updates.
boolean configChanged = mMergedConfiguration.equals(Configuration.EMPTY)
|| mTmpConfig.diff(mMergedConfiguration) != 0;
@@ -1612,7 +1616,7 @@
removeImmediately();
} else {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.onWindowReplacementTimeout();
}
}
@@ -1885,7 +1889,7 @@
mJustMovedInStack = true;
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.notifyMovedInStack();
}
}
@@ -1906,7 +1910,7 @@
mJustMovedInStack = false;
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.resetJustMovedInStack();
}
}
@@ -1929,7 +1933,7 @@
if (mInputChannel != null) {
throw new IllegalStateException("Window already has an input channel.");
}
- String name = makeInputChannelName();
+ String name = getName();
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
@@ -2001,7 +2005,7 @@
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
if (c.removeReplacedWindowIfNeeded(replacement)) {
return true;
}
@@ -2035,7 +2039,7 @@
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
replacementSet |= c.setReplacementWindowIfNeeded(replacementCandidate);
}
@@ -2408,7 +2412,7 @@
return true;
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
if (c.isAnimatingInvisibleWithSavedSurface()) {
return true;
}
@@ -2418,7 +2422,7 @@
void stopUsingSavedSurface() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.stopUsingSavedSurface();
}
@@ -2439,7 +2443,7 @@
mWinAnimator.mAnimating = true;
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.markSavedSurfaceExiting();
}
}
@@ -2448,7 +2452,7 @@
animators.add(mWinAnimator);
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.addWinAnimatorToList(animators);
}
}
@@ -2480,7 +2484,7 @@
public void clearWasVisibleBeforeClientHidden() {
mWasVisibleBeforeClientHidden = false;
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.clearWasVisibleBeforeClientHidden();
}
}
@@ -2492,7 +2496,7 @@
void onStartFreezingScreen() {
mAppFreezing = true;
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.onStartFreezingScreen();
}
}
@@ -2500,7 +2504,7 @@
boolean onStopFreezingScreen() {
boolean unfrozeWindows = false;
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
unfrozeWindows |= c.onStopFreezingScreen();
}
@@ -2570,7 +2574,7 @@
boolean destroySurface(boolean cleanupOnResume, boolean appStopped) {
boolean destroyedSomething = false;
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
destroyedSomething |= c.destroySurface(cleanupOnResume, appStopped);
}
@@ -2630,7 +2634,7 @@
void destroySavedSurface() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.destroySavedSurface();
}
@@ -2646,7 +2650,7 @@
int restoreSavedSurfaceForInterestingWindow() {
int interestingNotDrawn = -1;
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
final int childInterestingNotDrawn = c.restoreSavedSurfaceForInterestingWindow();
if (childInterestingNotDrawn != -1) {
if (interestingNotDrawn == -1) {
@@ -2717,7 +2721,7 @@
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
if (c.canRestoreSurface()) {
return true;
}
@@ -2837,6 +2841,8 @@
region.op(mTmpRect, Region.Op.INTERSECT);
}
+ // TODO: This is one reason why WindowList are bad...prime candidate for removal once we
+ // figure-out a good way to replace WindowList with WindowContainer hierarchy.
WindowList getWindowList() {
final DisplayContent displayContent = getDisplayContent();
return displayContent == null ? null : displayContent.getWindowList();
@@ -2893,8 +2899,7 @@
final Configuration overrideConfig = task != null
? task.mOverrideConfig
: Configuration.EMPTY;
- final Configuration serviceConfig = mService.mCurConfiguration;
- outConfig.setTo(serviceConfig);
+ outConfig.setTo(mService.mGlobalConfiguration);
if (overrideConfig != Configuration.EMPTY) {
outConfig.updateFrom(overrideConfig);
}
@@ -3323,7 +3328,8 @@
}
}
- String makeInputChannelName() {
+ @Override
+ String getName() {
return Integer.toHexString(System.identityHashCode(this))
+ " " + getWindowTag();
}
@@ -3455,8 +3461,8 @@
WindowState getBottomChild() {
// Child windows are z-ordered based on sub-layer using {@link #sWindowSubLayerComparator}
// and the child with the lowest z-order will be at the head of the list.
- WindowContainer c = mChildren.peekFirst();
- return c == null ? null : (WindowState)c;
+ WindowState c = mChildren.peekFirst();
+ return c == null ? null : c;
}
boolean layoutInParentFrame() {
@@ -3487,7 +3493,7 @@
void setWillReplaceWindow(boolean animate) {
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.setWillReplaceWindow(animate);
}
@@ -3509,7 +3515,7 @@
mAnimateReplacingWindow = false;
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.clearWillReplaceWindow();
}
}
@@ -3520,7 +3526,7 @@
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
if (c.waitingForReplacement()) {
return true;
}
@@ -3536,7 +3542,7 @@
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.requestUpdateWallpaperIfNeeded();
}
}
@@ -3584,7 +3590,7 @@
setWillReplaceWindow(false /* animate */);
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.setWillReplaceChildWindows();
}
}
@@ -3594,7 +3600,7 @@
return this;
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
final WindowState replacing = c.getReplacingWindow();
if (replacing != null) {
return replacing;
@@ -3642,7 +3648,7 @@
final DisplayContent displayContent = getDisplayContent();
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
if (c.mWinAnimator.mSurfaceController != null) {
c.performShowLocked();
// It hadn't been shown, which means layout not performed on it, so now we
@@ -3704,7 +3710,7 @@
windowInfo.childTokens = new ArrayList(childCount);
}
for (int j = 0; j < childCount; j++) {
- final WindowState child = (WindowState) mChildren.get(j);
+ final WindowState child = mChildren.get(j);
windowInfo.childTokens.add(child.mClient.asBinder());
}
}
@@ -3714,7 +3720,7 @@
int getHighestAnimLayer() {
int highest = mWinAnimator.mAnimLayer;
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
final int childLayer = c.getHighestAnimLayer();
if (childLayer > highest) {
highest = childLayer;
@@ -3728,7 +3734,7 @@
if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG_WM,
"adjustAnimLayer win=" + this + " anim layer: " + mWinAnimator.mAnimLayer);
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState childWindow = (WindowState) mChildren.get(i);
+ final WindowState childWindow = mChildren.get(i);
childWindow.adjustAnimLayer(adj);
if (childWindow.mWinAnimator.mAnimLayer > highestAnimLayer) {
highestAnimLayer = childWindow.mWinAnimator.mAnimLayer;
@@ -3737,6 +3743,16 @@
return highestAnimLayer;
}
+ @Override
+ int rebuildWindowList(DisplayContent dc, int addIndex) {
+ final DisplayContent winDisplayContent = getDisplayContent();
+ if (winDisplayContent == dc || winDisplayContent == null) {
+ mDisplayContent = dc;
+ return reAddWindow(addIndex);
+ }
+ return addIndex;
+ }
+
// TODO: come-up with a better name for this method that represents what it does.
// Or, it is probably not going to matter anyways if we are successful in getting rid of
// the WindowList concept.
@@ -3747,7 +3763,7 @@
final int childCount = mChildren.size();
boolean winAdded = false;
for (int j = 0; j < childCount; j++) {
- final WindowState child = (WindowState) mChildren.get(j);
+ final WindowState child = mChildren.get(j);
if (!winAdded && child.mSubLayer >= 0) {
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM,
"Re-adding child window at " + index + ": " + child);
@@ -3785,7 +3801,7 @@
int childCount = mChildren.size();
while (childCount > 0) {
childCount--;
- final WindowState cw = (WindowState) mChildren.get(childCount);
+ final WindowState cw = mChildren.get(childCount);
int cpos = windows.indexOf(cw);
if (cpos >= 0) {
if (cpos < interestingPos) interestingPos--;
@@ -3802,7 +3818,7 @@
return true;
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
if (c.isWindowAnimationSet()) {
return true;
}
@@ -3818,9 +3834,9 @@
if (!mChildren.isEmpty()) {
// Copying to a different list as multiple children can be removed.
// TODO: Not sure if we really need to copy this into a different list.
- final LinkedList childWindows = new LinkedList(mChildren);
+ final LinkedList<WindowState> childWindows = new LinkedList(mChildren);
for (int i = childWindows.size() - 1; i >= 0; i--) {
- ((WindowState)childWindows.get(i)).onExitAnimationDone();
+ childWindows.get(i).onExitAnimationDone();
}
}
@@ -3914,7 +3930,7 @@
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
- didSomething |= ((WindowState) mChildren.get(i)).clearAnimatingFlags();
+ didSomething |= (mChildren.get(i)).clearAnimatingFlags();
}
return didSomething;
@@ -3926,7 +3942,7 @@
void hideWallpaperWindow(boolean wasDeferred, String reason) {
for (int j = mChildren.size() - 1; j >= 0; --j) {
- final WindowState c = (WindowState) mChildren.get(j);
+ final WindowState c = mChildren.get(j);
c.hideWallpaperWindow(wasDeferred, reason);
}
if (!mWinAnimator.mLastHidden || wasDeferred) {
@@ -3966,7 +3982,7 @@
return true;
}
for (int j = mChildren.size() - 1; j >= 0; --j) {
- final WindowState c = (WindowState) mChildren.get(j);
+ final WindowState c = mChildren.get(j);
if (c.hasVisibleNotDrawnWallpaper()) {
return true;
}
@@ -3974,63 +3990,9 @@
return false;
}
- /** Places this window after the input window in the window list. */
- void addWindowToListAfter(WindowState pos) {
- final WindowList windows = pos.getWindowList();
- final int i = windows.indexOf(pos);
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Adding window " + this + " at " + (i+1) + " of " + windows.size()
- + " (after " + pos + ")");
- windows.add(i+1, this);
- mService.mWindowsChanged = true;
- }
-
- /** Places this window before the input window in the window list. */
- void addWindowToListBefore(WindowState pos) {
- final WindowList windows = pos.getWindowList();
- int i = windows.indexOf(pos);
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Adding window " + this + " at " + i + " of " + windows.size()
- + " (before " + pos + ")");
- if (i < 0) {
- Slog.w(TAG_WM, "addWindowToListBefore: Unable to find " + pos + " in " + windows);
- i = 0;
- }
- windows.add(i, this);
- mService.mWindowsChanged = true;
- }
-
- /** Adds this non-app window to the window list. */
- void addNonAppWindowToList() {
- final WindowList windows = getWindowList();
-
- // Figure out where window should go, based on layer.
- int i;
- for (i = windows.size() - 1; i >= 0; i--) {
- final WindowState otherWin = windows.get(i);
- if (otherWin.getBaseType() != TYPE_WALLPAPER && otherWin.mBaseLayer <= mBaseLayer) {
- // Wallpaper wanders through the window list, for example to position itself
- // directly behind keyguard. Because of this it will break the ordering based on
- // WindowState.mBaseLayer. There might windows with higher mBaseLayer behind it and
- // we don't want the new window to appear above them. An example of this is adding
- // of the docked stack divider. Consider a scenario with the following ordering (top
- // to bottom): keyguard, wallpaper, assist preview, apps. We want the dock divider
- // to land below the assist preview, so the dock divider must ignore the wallpaper,
- // with which it shares the base layer.
- break;
- }
- }
-
- i++;
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Free window: Adding window " + this + " at " + i + " of " + windows.size());
- windows.add(i, this);
- mService.mWindowsChanged = true;
- }
-
void updateReportedVisibility(UpdateReportedVisibilityResults results) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = (WindowState) mChildren.get(i);
+ final WindowState c = mChildren.get(i);
c.updateReportedVisibility(results);
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index b9956c8..788f28d 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -225,8 +225,6 @@
int mAttrType;
static final long PENDING_TRANSACTION_FINISH_WAIT_TIME = 100;
- long mDeferTransactionUntilFrame = -1;
- long mDeferTransactionTime = -1;
boolean mForceScaleUntilResize;
@@ -1889,35 +1887,10 @@
if (!mWin.isChildWindow()) {
return;
}
- mDeferTransactionUntilFrame = frameNumber;
- mDeferTransactionTime = System.currentTimeMillis();
mSurfaceController.deferTransactionUntil(
mWin.getParentWindow().mWinAnimator.mSurfaceController.getHandle(), frameNumber);
}
- // Defer the current transaction to the frame number of the last saved transaction.
- // We do this to avoid shooting through an unsynchronized transaction while something is
- // pending. This is generally fine, as either we will get in on the synchronization,
- // or SurfaceFlinger will see that the frame has already occured. The only
- // potential problem is in frame number resets so we reset things with a timeout
- // every so often to be careful.
- void deferToPendingTransaction() {
- if (mDeferTransactionUntilFrame < 0) {
- return;
- }
- final WindowState parentWindow = mWin.getParentWindow();
- long time = System.currentTimeMillis();
- if (time > mDeferTransactionTime + PENDING_TRANSACTION_FINISH_WAIT_TIME) {
- mDeferTransactionTime = -1;
- mDeferTransactionUntilFrame = -1;
- } else if (parentWindow != null &&
- parentWindow.mWinAnimator.hasSurface()) {
- mSurfaceController.deferTransactionUntil(
- mWin.getParentWindow().mWinAnimator.mSurfaceController.getHandle(),
- mDeferTransactionUntilFrame);
- }
- }
-
/**
* Sometimes we need to synchronize the first frame of animation with some external event.
* To achieve this, we prolong the start of the animation and keep producing the first frame of
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 317bb35..5025df6 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -37,7 +37,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.DO_TRAVERSAL;
-import static com.android.server.wm.WindowManagerService.H.NOTIFY_ACTIVITY_DRAWN;
import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
import static com.android.server.wm.WindowManagerService.H.NOTIFY_STARTING_WINDOW_DRAWN;
import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
@@ -560,7 +559,7 @@
// Remove all deferred displays stacks, tasks, and activities.
for (int displayNdx = mService.mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
- mService.mDisplayContents.valueAt(displayNdx).checkForDeferredActions();
+ mService.mDisplayContents.valueAt(displayNdx).checkCompleteDeferredRemoval();
}
if (updateInputWindowsNeeded) {
@@ -606,8 +605,6 @@
final int displayId = displayContent.getDisplayId();
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
- final int innerDw = displayInfo.appWidth;
- final int innerDh = displayInfo.appHeight;
final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
// Reset for each display.
@@ -741,10 +738,6 @@
// Moved from updateWindowsAndWallpaperLocked().
if (w.mHasSurface) {
- // If we have recently synchronized a previous transaction for this
- // window ensure we don't push through an unsynchronized one now.
- winAnimator.deferToPendingTransaction();
-
// Take care of the window being ready to display.
final boolean committed = winAnimator.commitFinishDrawingLocked();
if (isDefaultDisplay && committed) {
@@ -868,10 +861,12 @@
mPreferredModeId,
true /* inTraversal, must call performTraversalInTrans... below */);
- mService.getDisplayContentLocked(displayId).stopDimmingIfNeeded();
+ displayContent.stopDimmingIfNeeded();
if (updateAllDrawn) {
- updateAllDrawnLocked(displayContent);
+ // See if any windows have been drawn, so they (and others associated with them)
+ // can now be shown.
+ displayContent.updateAllDrawn();
}
}
@@ -919,7 +914,7 @@
}
mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mService.mRotation,
- mService.mCurConfiguration.uiMode);
+ mService.mGlobalConfiguration.uiMode);
if (isDefaultDisplay) {
// Not needed on non-default displays.
mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
@@ -1085,7 +1080,8 @@
mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
- mService.rebuildAppWindowListLocked();
+ final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
+ displayContent.rebuildAppWindowList();
mWallpaperMayChange = false;
@@ -1109,7 +1105,6 @@
}
// Adjust wallpaper before we pull the lower/upper target, since pending changes
// (like the clearAnimatingFlags() above) might affect wallpaper target result.
- final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
if ((displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0 &&
mWallpaperControllerLocked.adjustWallpaperWindows()) {
mService.mLayersController.assignLayersLocked(windows);
@@ -1160,7 +1155,7 @@
voiceInteraction |= wtoken.voiceInteraction;
- if (wtoken.appFullscreen) {
+ if (wtoken.fillsParent()) {
WindowState ws = wtoken.findMainWindow();
if (ws != null) {
animLp = ws.mAttrs;
@@ -1522,52 +1517,6 @@
}
}
- private void updateAllDrawnLocked(DisplayContent displayContent) {
- // See if any windows have been drawn, so they (and others
- // associated with them) can now be shown.
- ArrayList<TaskStack> stacks = displayContent.getStacks();
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ArrayList<Task> tasks = stacks.get(stackNdx).getTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
- for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
- final AppWindowToken wtoken = tokens.get(tokenNdx);
- if (!wtoken.allDrawn) {
- int numInteresting = wtoken.numInterestingWindows;
- if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) {
- if (DEBUG_VISIBILITY)
- Slog.v(TAG, "allDrawn: " + wtoken
- + " interesting=" + numInteresting
- + " drawn=" + wtoken.numDrawnWindows);
- wtoken.allDrawn = true;
- // Force an additional layout pass where WindowStateAnimator#
- // commitFinishDrawingLocked() will call performShowLocked().
- displayContent.layoutNeeded = true;
- mService.mH.obtainMessage(NOTIFY_ACTIVITY_DRAWN,
- wtoken.token).sendToTarget();
- }
- }
- if (!wtoken.allDrawnExcludingSaved) {
- int numInteresting = wtoken.numInterestingWindowsExcludingSaved;
- if (numInteresting > 0
- && wtoken.numDrawnWindowsExcludingSaved >= numInteresting) {
- if (DEBUG_VISIBILITY)
- Slog.v(TAG, "allDrawnExcludingSaved: " + wtoken
- + " interesting=" + numInteresting
- + " drawn=" + wtoken.numDrawnWindowsExcludingSaved);
- wtoken.allDrawnExcludingSaved = true;
- displayContent.layoutNeeded = true;
- if (wtoken.isAnimatingInvisibleWithSavedSurface()
- && !mService.mFinishedEarlyAnim.contains(wtoken)) {
- mService.mFinishedEarlyAnim.add(wtoken);
- }
- }
- }
- }
- }
- }
- }
-
private static int toBrightnessOverride(float value) {
return (int)(value * PowerManager.BRIGHTNESS_ON);
}
@@ -1575,8 +1524,7 @@
private void processApplicationsAnimatingInPlace(int transit) {
if (transit == AppTransition.TRANSIT_TASK_IN_PLACE) {
// Find the focused window
- final WindowState win = mService.findFocusedWindowLocked(
- mService.getDefaultDisplayContentLocked());
+ final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow();
if (win != null) {
final AppWindowToken wtoken = win.mAppToken;
final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
@@ -1646,8 +1594,8 @@
// synchronize its thumbnail surface with the surface for the
// open/close animation (only on the way down)
anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
- insets, thumbnailHeader, taskId, mService.mCurConfiguration.uiMode,
- mService.mCurConfiguration.orientation);
+ insets, thumbnailHeader, taskId, mService.mGlobalConfiguration.uiMode,
+ mService.mGlobalConfiguration.orientation);
openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
openingAppAnimator.deferThumbnailDestruction =
!mService.mAppTransition.isNextThumbnailTransitionScaleUp();
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5f9bb9a..735efa9 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -22,15 +22,11 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
-import android.view.IWindow;
import java.io.PrintWriter;
-import java.util.ArrayList;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
@@ -46,7 +42,7 @@
* which is the handle for an Activity that it uses to display windows. For nested windows, there is
* a WindowToken created for the parent window to manage its children.
*/
-class WindowToken extends WindowContainer {
+class WindowToken extends WindowContainer<WindowState> {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowToken" : TAG_WM;
// The window manager!
@@ -92,7 +88,7 @@
void removeAllWindows() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState win = (WindowState) mChildren.get(i);
+ final WindowState win = mChildren.get(i);
if (DEBUG_WINDOW_MOVEMENT) Slog.w(TAG_WM, "removeAllWindows: removing win=" + win);
win.removeIfPossible();
}
@@ -110,7 +106,7 @@
DisplayContent displayContent = null;
for (int i = 0; i < count; i++) {
- final WindowState win = (WindowState) mChildren.get(i);
+ final WindowState win = mChildren.get(i);
if (win.mWinAnimator.isAnimationSet()) {
delayed = true;
// TODO: This is technically wrong as a token can have windows on multi-displays
@@ -135,7 +131,7 @@
int adjustAnimLayer(int adj) {
int highestAnimLayer = -1;
for (int j = mChildren.size() - 1; j >= 0; j--) {
- final WindowState w = (WindowState) mChildren.get(j);
+ final WindowState w = mChildren.get(j);
final int winHighestAnimLayer = w.adjustAnimLayer(adj);
if (winHighestAnimLayer > highestAnimLayer) {
highestAnimLayer = winHighestAnimLayer;
@@ -147,7 +143,6 @@
return highestAnimLayer;
}
- /* package level access for test. */
WindowState getTopWindow() {
if (mChildren.isEmpty()) {
return null;
@@ -160,10 +155,9 @@
* @param target The window to search for.
* @return The index of win in windows or of the window that is an ancestor of win.
*/
- /* package level access for test. */
int getWindowIndex(WindowState target) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
if (w == target || w.hasChild(target)) {
return i;
}
@@ -171,290 +165,34 @@
return -1;
}
- /**
- * Return the list of Windows in this token on the given Display.
- * @param displayContent The display we are interested in.
- * @return List of windows from token that are on displayContent.
- */
- private WindowList getTokenWindowsOnDisplay(DisplayContent displayContent) {
- final WindowList windowList = new WindowList();
- final WindowList displayWindows = displayContent.getWindowList();
- final int count = displayWindows.size();
- for (int i = 0; i < count; i++) {
- final WindowState win = displayWindows.get(i);
- if (win.mToken == this) {
- windowList.add(win);
- }
- }
- return windowList;
- }
-
- // TODO: Now that we are no longer adding child windows to token directly, the rest of the code
- // in this method doesn't really belong here, but is it difficult to move at the moment. Need to
- // re-evaluate when figuring-out what to do about display window list.
- private void addChildWindow(final WindowState win) {
- final DisplayContent displayContent = win.getDisplayContent();
- if (displayContent == null) {
- return;
- }
- final WindowState parentWindow = win.getParentWindow();
-
- WindowList windowsOnSameDisplay = getTokenWindowsOnDisplay(displayContent);
-
- // Figure out this window's ordering relative to the parent window.
- final int wCount = windowsOnSameDisplay.size();
- final int sublayer = win.mSubLayer;
- int largestSublayer = Integer.MIN_VALUE;
- WindowState windowWithLargestSublayer = null;
- int i;
- for (i = 0; i < wCount; i++) {
- WindowState w = windowsOnSameDisplay.get(i);
- final int wSublayer = w.mSubLayer;
- if (wSublayer >= largestSublayer) {
- largestSublayer = wSublayer;
- windowWithLargestSublayer = w;
- }
- if (sublayer < 0) {
- // For negative sublayers, we go below all windows in the same sublayer.
- if (wSublayer >= sublayer) {
- win.addWindowToListBefore(wSublayer >= 0 ? parentWindow : w);
- break;
- }
- } else {
- // For positive sublayers, we go above all windows in the same sublayer.
- if (wSublayer > sublayer) {
- win.addWindowToListBefore(w);
- break;
- }
- }
- }
- if (i >= wCount) {
- if (sublayer < 0) {
- win.addWindowToListBefore(parentWindow);
- } else {
- win.addWindowToListAfter(
- largestSublayer >= 0 ? windowWithLargestSublayer : parentWindow);
- }
- }
- }
-
void addWindow(final WindowState win) {
if (DEBUG_FOCUS) Slog.d(TAG_WM, "addWindow: win=" + win + " Callers=" + Debug.getCallers(5));
+ final DisplayContent dc = win.getDisplayContent();
if (!win.isChildWindow()) {
int tokenWindowsPos = 0;
- if (asAppWindowToken() != null) {
- tokenWindowsPos = addAppWindow(win);
- } else {
- win.addNonAppWindowToList();
+ if (dc != null) {
+ if (asAppWindowToken() != null) {
+ tokenWindowsPos = dc.addAppWindowToWindowList(win);
+ } else {
+ dc.addNonAppWindowToWindowList(win);
+ }
}
if (!mChildren.contains(win)) {
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
- mChildren.add(tokenWindowsPos, win);
+ addChild(win, tokenWindowsPos);
}
- } else {
- addChildWindow(win);
+ } else if (dc != null) {
+ dc.addChildWindowToWindowList(win);
}
}
- private int addAppWindow(final WindowState win) {
- final DisplayContent displayContent = win.getDisplayContent();
- if (displayContent == null) {
- // It doesn't matter this display is going away.
- return 0;
- }
- final IWindow client = win.mClient;
-
- final WindowList windows = displayContent.getWindowList();
- WindowList tokenWindowList = getTokenWindowsOnDisplay(displayContent);
- int tokenWindowsPos = 0;
- if (!tokenWindowList.isEmpty()) {
- return addAppWindowExisting(win, windows, tokenWindowList);
- }
-
- // No windows from this token on this display
- if (mService.localLOGV) Slog.v(TAG_WM, "Figuring out where to add app window "
- + client.asBinder() + " (token=" + this + ")");
-
- // Figure out where the window should go, based on the order of applications.
- WindowState pos = null;
-
- final ArrayList<Task> tasks = displayContent.getTasks();
- int taskNdx;
- int tokenNdx = -1;
- for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
- for (tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
- final AppWindowToken t = tokens.get(tokenNdx);
- if (t == this) {
- --tokenNdx;
- if (tokenNdx < 0) {
- --taskNdx;
- if (taskNdx >= 0) {
- tokenNdx = tasks.get(taskNdx).mAppTokens.size() - 1;
- }
- }
- break;
- }
-
- // We haven't reached the token yet; if this token is not going to the bottom and
- // has windows on this display, we can use it as an anchor for when we do reach the
- // token.
- tokenWindowList = getTokenWindowsOnDisplay(displayContent);
- if (!t.sendingToBottom && tokenWindowList.size() > 0) {
- pos = tokenWindowList.get(0);
- }
- }
- if (tokenNdx >= 0) {
- // early exit
- break;
- }
- }
-
- // We now know the index into the apps. If we found an app window above, that gives us the
- // position; else we need to look some more.
- if (pos != null) {
- // Move behind any windows attached to this one.
- final WindowToken atoken = mService.mTokenMap.get(pos.mClient.asBinder());
- if (atoken != null) {
- tokenWindowList = atoken.getTokenWindowsOnDisplay(displayContent);
- final int NC = tokenWindowList.size();
- if (NC > 0) {
- WindowState bottom = tokenWindowList.get(0);
- if (bottom.mSubLayer < 0) {
- pos = bottom;
- }
- }
- }
- win.addWindowToListBefore(pos);
- return tokenWindowsPos;
- }
-
- // Continue looking down until we find the first token that has windows on this display.
- for ( ; taskNdx >= 0; --taskNdx) {
- AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
- for ( ; tokenNdx >= 0; --tokenNdx) {
- final WindowToken t = tokens.get(tokenNdx);
- tokenWindowList = t.getTokenWindowsOnDisplay(displayContent);
- final int NW = tokenWindowList.size();
- if (NW > 0) {
- pos = tokenWindowList.get(NW-1);
- break;
- }
- }
- if (tokenNdx >= 0) {
- // found
- break;
- }
- }
-
- if (pos != null) {
- // Move in front of any windows attached to this one.
- final WindowToken atoken = mService.mTokenMap.get(pos.mClient.asBinder());
- if (atoken != null) {
- final WindowState top = atoken.getTopWindow();
- if (top != null && top.mSubLayer >= 0) {
- pos = top;
- }
- }
- win.addWindowToListAfter(pos);
- return tokenWindowsPos;
- }
-
- // Just search for the start of this layer.
- final int myLayer = win.mBaseLayer;
- int i;
- for (i = windows.size() - 1; i >= 0; --i) {
- WindowState w = windows.get(i);
- // Dock divider shares the base layer with application windows, but we want to always
- // keep it above the application windows. The sharing of the base layer is intended
- // for window animations, which need to be above the dock divider for the duration
- // of the animation.
- if (w.mBaseLayer <= myLayer && w.mAttrs.type != TYPE_DOCK_DIVIDER) {
- break;
- }
- }
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Based on layer: Adding window " + win + " at " + (i + 1) + " of "
- + windows.size());
- windows.add(i + 1, win);
- mService.mWindowsChanged = true;
- return tokenWindowsPos;
- }
-
- private int addAppWindowExisting(
- WindowState win, WindowList windowList, WindowList tokenWindowList) {
-
- int tokenWindowsPos;
- // If this application has existing windows, we simply place the new window on top of
- // them... but keep the starting window on top.
- if (win.mAttrs.type == TYPE_BASE_APPLICATION) {
- // Base windows go behind everything else.
- final WindowState lowestWindow = tokenWindowList.get(0);
- win.addWindowToListBefore(lowestWindow);
- tokenWindowsPos = getWindowIndex(lowestWindow);
- } else {
- final AppWindowToken atoken = win.mAppToken;
- final int windowListPos = tokenWindowList.size();
- final WindowState lastWindow = tokenWindowList.get(windowListPos - 1);
- if (atoken != null && lastWindow == atoken.startingWindow) {
- win.addWindowToListBefore(lastWindow);
- tokenWindowsPos = getWindowIndex(lastWindow);
- } else {
- int newIdx = findIdxBasedOnAppTokens(win);
- // There is a window above this one associated with the same apptoken note that the
- // window could be a floating window that was created later or a window at the top
- // of the list of windows associated with this token.
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of "
- + windowList.size());
- windowList.add(newIdx + 1, win);
- if (newIdx < 0) {
- // No window from token found on win's display.
- tokenWindowsPos = 0;
- } else {
- tokenWindowsPos = getWindowIndex(windowList.get(newIdx)) + 1;
- }
- mService.mWindowsChanged = true;
- }
- }
- return tokenWindowsPos;
- }
-
- int reAddAppWindows(DisplayContent displayContent, int index) {
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
- final WindowState win = (WindowState) mChildren.get(i);
- final DisplayContent winDisplayContent = win.getDisplayContent();
- if (winDisplayContent == displayContent || winDisplayContent == null) {
- win.mDisplayContent = displayContent;
- index = win.reAddWindow(index);
- }
- }
- return index;
- }
-
- /**
- * This method finds out the index of a window that has the same app token as win. used for z
- * ordering the windows in mWindows
- */
- private int findIdxBasedOnAppTokens(WindowState win) {
- final WindowList windows = win.getWindowList();
- for(int j = windows.size() - 1; j >= 0; j--) {
- final WindowState wentry = windows.get(j);
- if(wentry.mAppToken == win.mAppToken) {
- return j;
- }
- }
- return -1;
- }
-
/** Return the first window in the token window list that isn't a starting window or null. */
WindowState getFirstNonStartingWindow() {
final int count = mChildren.size();
// We only care about parent windows so no need to loop through child windows.
for (int i = 0; i < count; i++) {
- final WindowState w = (WindowState) mChildren.get(i);
+ final WindowState w = mChildren.get(i);
if (w.mAttrs.type != TYPE_APPLICATION_STARTING) {
return w;
}
@@ -474,7 +212,7 @@
WindowState getReplacingWindow() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState win = (WindowState) mChildren.get(i);
+ final WindowState win = mChildren.get(i);
final WindowState replacing = win.getReplacingWindow();
if (replacing != null) {
return replacing;
@@ -485,7 +223,7 @@
void hideWallpaperToken(boolean wasDeferred, String reason) {
for (int j = mChildren.size() - 1; j >= 0; j--) {
- final WindowState wallpaper = (WindowState) mChildren.get(j);
+ final WindowState wallpaper = mChildren.get(j);
wallpaper.hideWallpaperWindow(wasDeferred, reason);
}
hidden = true;
@@ -494,7 +232,7 @@
void sendWindowWallpaperCommand(
String action, int x, int y, int z, Bundle extras, boolean sync) {
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
- final WindowState wallpaper = (WindowState) mChildren.get(wallpaperNdx);
+ final WindowState wallpaper = mChildren.get(wallpaperNdx);
try {
wallpaper.mClient.dispatchWallpaperCommand(action, x, y, z, extras, sync);
// We only want to be synchronous with one wallpaper.
@@ -507,7 +245,7 @@
void updateWallpaperOffset(int dw, int dh, boolean sync) {
final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
- final WindowState wallpaper = (WindowState) mChildren.get(wallpaperNdx);
+ final WindowState wallpaper = mChildren.get(wallpaperNdx);
if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) {
final WindowStateAnimator winAnimator = wallpaper.mWinAnimator;
winAnimator.computeShownFrameLocked();
@@ -528,7 +266,7 @@
final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
- final WindowState wallpaper = (WindowState) mChildren.get(wallpaperNdx);
+ final WindowState wallpaper = mChildren.get(wallpaperNdx);
if (visible) {
wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
}
@@ -551,7 +289,7 @@
final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
- final WindowState wallpaper = (WindowState) mChildren.get(wallpaperNdx);
+ final WindowState wallpaper = mChildren.get(wallpaperNdx);
if (visible) {
wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
@@ -627,7 +365,7 @@
boolean hasVisibleNotDrawnWallpaper() {
for (int j = mChildren.size() - 1; j >= 0; --j) {
- final WindowState wallpaper = (WindowState) mChildren.get(j);
+ final WindowState wallpaper = mChildren.get(j);
if (wallpaper.hasVisibleNotDrawnWallpaper()) {
return true;
}
@@ -638,7 +376,7 @@
int getHighestAnimLayer() {
int highest = -1;
for (int j = 0; j < mChildren.size(); j++) {
- final WindowState w = (WindowState) mChildren.get(j);
+ final WindowState w = mChildren.get(j);
final int wLayer = w.getHighestAnimLayer();
if (wLayer > highest) {
highest = wLayer;
@@ -675,4 +413,9 @@
}
return stringName;
}
+
+ @Override
+ String getName() {
+ return toString();
+ }
}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 3cc9ec6..9459517 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -38,8 +38,6 @@
frameworks/base/libs/hwui \
frameworks/base/core/jni \
frameworks/native/services \
- libcore/include \
- libcore/include/libsuspend \
system/security/keystore/include \
$(call include-path-for, libhardware)/hardware \
$(call include-path-for, libhardware_legacy)/hardware_legacy \
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f9129ee..af84c2a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -309,6 +309,12 @@
private static final int DEVICE_ADMIN_DEACTIVATE_TIMEOUT = 10000;
+ /**
+ * Minimum timeout in milliseconds after which unlocking with weak auth times out,
+ * i.e. the user has to use a strong authentication method like password, PIN or pattern.
+ */
+ private static final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = 1 * 60 * 60 * 1000; // 1h
+
final Context mContext;
final Injector mInjector;
final IPackageManager mIPackageManager;
@@ -525,6 +531,7 @@
static class ActiveAdmin {
private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
+ private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin";
private static final String TAG_DISABLE_CAMERA = "disable-camera";
private static final String TAG_DISABLE_CALLER_ID = "disable-caller-id";
private static final String TAG_DISABLE_CONTACTS_SEARCH = "disable-contacts-search";
@@ -549,6 +556,7 @@
private static final String TAG_PERMITTED_IMES = "permitted-imes";
private static final String TAG_MAX_FAILED_PASSWORD_WIPE = "max-failed-password-wipe";
private static final String TAG_MAX_TIME_TO_UNLOCK = "max-time-to-unlock";
+ private static final String TAG_STRONG_AUTH_UNLOCK_TIMEOUT = "strong-auth-unlock-timeout";
private static final String TAG_MIN_PASSWORD_NONLETTER = "min-password-nonletter";
private static final String TAG_MIN_PASSWORD_SYMBOLS = "min-password-symbols";
private static final String TAG_MIN_PASSWORD_NUMERIC = "min-password-numeric";
@@ -603,6 +611,8 @@
static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
+ long strongAuthUnlockTimeout = DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
+
static final int DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE = 0;
int maximumFailedPasswordsForWipe = DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE;
@@ -617,6 +627,7 @@
int disabledKeyguardFeatures = DEF_KEYGUARD_FEATURES_DISABLED;
boolean encryptionRequested = false;
+ boolean testOnlyAdmin = false;
boolean disableCamera = false;
boolean disableCallerId = false;
boolean disableContactsSearch = false;
@@ -751,6 +762,11 @@
out.attribute(null, ATTR_VALUE, Long.toString(maximumTimeToUnlock));
out.endTag(null, TAG_MAX_TIME_TO_UNLOCK);
}
+ if (strongAuthUnlockTimeout != DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
+ out.startTag(null, TAG_STRONG_AUTH_UNLOCK_TIMEOUT);
+ out.attribute(null, ATTR_VALUE, Long.toString(strongAuthUnlockTimeout));
+ out.endTag(null, TAG_STRONG_AUTH_UNLOCK_TIMEOUT);
+ }
if (maximumFailedPasswordsForWipe != DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
out.startTag(null, TAG_MAX_FAILED_PASSWORD_WIPE);
out.attribute(null, ATTR_VALUE, Integer.toString(maximumFailedPasswordsForWipe));
@@ -786,6 +802,11 @@
out.attribute(null, ATTR_VALUE, Boolean.toString(encryptionRequested));
out.endTag(null, TAG_ENCRYPTION_REQUESTED);
}
+ if (testOnlyAdmin) {
+ out.startTag(null, TAG_TEST_ONLY_ADMIN);
+ out.attribute(null, ATTR_VALUE, Boolean.toString(testOnlyAdmin));
+ out.endTag(null, TAG_TEST_ONLY_ADMIN);
+ }
if (disableCamera) {
out.startTag(null, TAG_DISABLE_CAMERA);
out.attribute(null, ATTR_VALUE, Boolean.toString(disableCamera));
@@ -960,6 +981,9 @@
} else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
maximumTimeToUnlock = Long.parseLong(
parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_STRONG_AUTH_UNLOCK_TIMEOUT.equals(tag)) {
+ strongAuthUnlockTimeout = Long.parseLong(
+ parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MAX_FAILED_PASSWORD_WIPE.equals(tag)) {
maximumFailedPasswordsForWipe = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
@@ -981,6 +1005,9 @@
} else if (TAG_ENCRYPTION_REQUESTED.equals(tag)) {
encryptionRequested = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_TEST_ONLY_ADMIN.equals(tag)) {
+ testOnlyAdmin = Boolean.parseBoolean(
+ parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_DISABLE_CAMERA.equals(tag)) {
disableCamera = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_VALUE));
@@ -1179,6 +1206,8 @@
void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("uid="); pw.println(getUid());
+ pw.print(prefix); pw.print("testOnlyAdmin=");
+ pw.println(testOnlyAdmin);
pw.print(prefix); pw.println("policies:");
ArrayList<DeviceAdminInfo.PolicyInfo> pols = info.getUsedPolicies();
if (pols != null) {
@@ -1206,6 +1235,8 @@
pw.println(minimumPasswordNonLetter);
pw.print(prefix); pw.print("maximumTimeToUnlock=");
pw.println(maximumTimeToUnlock);
+ pw.print(prefix); pw.print("strongAuthUnlockTimeout=");
+ pw.println(strongAuthUnlockTimeout);
pw.print(prefix); pw.print("maximumFailedPasswordsForWipe=");
pw.println(maximumFailedPasswordsForWipe);
pw.print(prefix); pw.print("specifiesGlobalProxy=");
@@ -2829,8 +2860,9 @@
synchronized (this) {
long ident = mInjector.binderClearCallingIdentity();
try {
- if (!refreshing
- && getActiveAdminUncheckedLocked(adminReceiver, userHandle) != null) {
+ final ActiveAdmin existingAdmin
+ = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
+ if (!refreshing && existingAdmin != null) {
throw new IllegalArgumentException("Admin is already added");
}
if (policy.mRemovingAdmins.contains(adminReceiver)) {
@@ -2838,6 +2870,9 @@
"Trying to set an admin which is being removed");
}
ActiveAdmin newAdmin = new ActiveAdmin(info, /* parent */ false);
+ newAdmin.testOnlyAdmin =
+ (existingAdmin != null) ? existingAdmin.testOnlyAdmin
+ : isPackageTestOnly(adminReceiver.getPackageName(), userHandle);
policy.mAdminMap.put(adminReceiver, newAdmin);
int replaceIndex = -1;
final int N = policy.mAdminList.size();
@@ -2949,12 +2984,13 @@
enforceShell("forceRemoveActiveAdmin");
long ident = mInjector.binderClearCallingIdentity();
try {
- if (!isPackageTestOnly(adminReceiver.getPackageName(), userHandle)) {
- throw new SecurityException("Attempt to remove non-test admin "
- + adminReceiver + " " + userHandle);
- }
- // If admin is a device or profile owner tidy that up first.
synchronized (this) {
+ if (!isAdminTestOnlyLocked(adminReceiver, userHandle)) {
+ throw new SecurityException("Attempt to remove non-test admin "
+ + adminReceiver + " " + userHandle);
+ }
+
+ // If admin is a device or profile owner tidy that up first.
if (isDeviceOwner(adminReceiver, userHandle)) {
clearDeviceOwnerLocked(getDeviceOwnerAdminLocked(), userHandle);
}
@@ -2972,6 +3008,17 @@
}
}
+ /**
+ * Return if a given package has testOnly="true", in which case we'll relax certain rules
+ * for CTS.
+ *
+ * DO NOT use this method except in {@link #setActiveAdmin}. Use {@link #isAdminTestOnlyLocked}
+ * to check wehter an active admin is test-only or not.
+ *
+ * The system allows this flag to be changed when an app is updated, which is not good
+ * for us. So we persist the flag in {@link ActiveAdmin} when an admin is first installed,
+ * and used the persisted version in actual checks. (See b/31382361 and b/28928996)
+ */
private boolean isPackageTestOnly(String packageName, int userHandle) {
final ApplicationInfo ai;
try {
@@ -2988,6 +3035,14 @@
return (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0;
}
+ /**
+ * See {@link #isPackageTestOnly}.
+ */
+ private boolean isAdminTestOnlyLocked(ComponentName who, int userHandle) {
+ final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
+ return (admin != null) && admin.testOnlyAdmin;
+ }
+
private void enforceShell(String method) {
final int callingUid = Binder.getCallingUid();
if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
@@ -4187,6 +4242,60 @@
}
@Override
+ public void setRequiredStrongAuthTimeout(ComponentName who, long timeoutMs,
+ boolean parent) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkArgument(timeoutMs >= MINIMUM_STRONG_AUTH_TIMEOUT_MS,
+ "Timeout must not be lower than the minimum strong auth timeout.");
+ Preconditions.checkArgument(timeoutMs <= DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS,
+ "Timeout must not be higher than the default strong auth timeout.");
+
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ synchronized (this) {
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+ if (ap.strongAuthUnlockTimeout != timeoutMs) {
+ ap.strongAuthUnlockTimeout = timeoutMs;
+ saveSettingsLocked(userHandle);
+ }
+ }
+ }
+
+ /**
+ * Return a single admin's strong auth unlock timeout or minimum value (strictest) of all
+ * admins if who is null.
+ * Returns default timeout if not configured.
+ */
+ @Override
+ public long getRequiredStrongAuthTimeout(ComponentName who, int userId, boolean parent) {
+ if (!mHasFeature) {
+ return DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
+ }
+ enforceFullCrossUsersPermission(userId);
+ synchronized (this) {
+ if (who != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userId, parent);
+ return admin != null ? Math.max(admin.strongAuthUnlockTimeout,
+ MINIMUM_STRONG_AUTH_TIMEOUT_MS)
+ : DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
+ }
+
+ // Return the strictest policy across all participating admins.
+ List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userId, parent);
+
+ long strongAuthUnlockTimeout = DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
+ for (int i = 0; i < admins.size(); i++) {
+ strongAuthUnlockTimeout = Math.min(admins.get(i).strongAuthUnlockTimeout,
+ strongAuthUnlockTimeout);
+ }
+ return Math.max(strongAuthUnlockTimeout, MINIMUM_STRONG_AUTH_TIMEOUT_MS);
+ }
+ }
+
+ @Override
public void lockNow(boolean parent) {
if (!mHasFeature) {
return;
@@ -6251,7 +6360,7 @@
* The profile owner can only be set before the user setup phase has completed,
* except for:
* - SYSTEM_UID
- * - adb if there are no accounts. (But see {@link #hasIncompatibleAccounts})
+ * - adb if there are no accounts. (But see {@link #hasIncompatibleAccountsLocked})
*/
private void enforceCanSetProfileOwnerLocked(@Nullable ComponentName owner, int userHandle) {
UserInfo info = getUserInfo(userHandle);
@@ -6274,7 +6383,7 @@
int callingUid = mInjector.binderGetCallingUid();
if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
if (hasUserSetupCompleted(userHandle)
- && hasIncompatibleAccounts(userHandle, owner)) {
+ && hasIncompatibleAccountsLocked(userHandle, owner)) {
throw new IllegalStateException("Not allowed to set the profile owner because "
+ "there are already some accounts on the profile");
}
@@ -6298,7 +6407,7 @@
enforceCanManageProfileAndDeviceOwners();
}
- final int code = checkSetDeviceOwnerPreCondition(owner, userId, isAdb);
+ final int code = checkSetDeviceOwnerPreConditionLocked(owner, userId, isAdb);
switch (code) {
case CODE_OK:
return;
@@ -8566,7 +8675,7 @@
* The device owner can only be set before the setup phase of the primary user has completed,
* except for adb command if no accounts or additional users are present on the device.
*/
- private synchronized @DeviceOwnerPreConditionCode int checkSetDeviceOwnerPreCondition(
+ private synchronized @DeviceOwnerPreConditionCode int checkSetDeviceOwnerPreConditionLocked(
@Nullable ComponentName owner, int deviceOwnerUserId, boolean isAdb) {
if (mOwners.hasDeviceOwner()) {
return CODE_HAS_DEVICE_OWNER;
@@ -8584,7 +8693,7 @@
if (mUserManager.getUserCount() > 1) {
return CODE_NONSYSTEM_USER_EXISTS;
}
- if (hasIncompatibleAccounts(UserHandle.USER_SYSTEM, owner)) {
+ if (hasIncompatibleAccountsLocked(UserHandle.USER_SYSTEM, owner)) {
return CODE_ACCOUNTS_NOT_EMPTY;
}
} else {
@@ -8610,8 +8719,10 @@
}
private boolean isDeviceOwnerProvisioningAllowed(int deviceOwnerUserId) {
- return CODE_OK == checkSetDeviceOwnerPreCondition(
- /* owner unknown */ null, deviceOwnerUserId, /* isAdb */ false);
+ synchronized (this) {
+ return CODE_OK == checkSetDeviceOwnerPreConditionLocked(
+ /* owner unknown */ null, deviceOwnerUserId, /* isAdb */ false);
+ }
}
private boolean hasFeatureManagedUsers() {
@@ -9257,7 +9368,7 @@
* ..._DISALLOWED, return true.
* - Otherwise return false.
*/
- private boolean hasIncompatibleAccounts(int userId, @Nullable ComponentName owner) {
+ private boolean hasIncompatibleAccountsLocked(int userId, @Nullable ComponentName owner) {
final long token = mInjector.binderClearCallingIdentity();
try {
final AccountManager am = AccountManager.get(mContext);
@@ -9295,7 +9406,7 @@
// Owner is unknown. Suppose it's not test-only
compatible = false;
log = "Only test-only device/profile owner can be installed with accounts";
- } else if (isPackageTestOnly(owner.getPackageName(), userId)) {
+ } else if (isAdminTestOnlyLocked(owner, userId)) {
if (compatible) {
log = "Installing test-only owner " + owner;
} else {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4591fcc..c5e2840 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -591,6 +591,9 @@
boolean disableTextServices = SystemProperties.getBoolean("config.disable_textservices", false);
boolean disableSamplingProfiler = SystemProperties.getBoolean("config.disable_samplingprof",
false);
+
+ boolean disableConsumerIr = SystemProperties.getBoolean("config.disable_consumerir", false);
+
boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1");
try {
@@ -646,10 +649,12 @@
ServiceManager.addService("vibrator", vibrator);
traceEnd();
- traceBeginAndSlog("StartConsumerIrService");
- consumerIr = new ConsumerIrService(context);
- ServiceManager.addService(Context.CONSUMER_IR_SERVICE, consumerIr);
- traceEnd();
+ if (!disableConsumerIr) {
+ traceBeginAndSlog("StartConsumerIrService");
+ consumerIr = new ConsumerIrService(context);
+ ServiceManager.addService(Context.CONSUMER_IR_SERVICE, consumerIr);
+ traceEnd();
+ }
traceBeginAndSlog("StartAlarmManagerService");
mSystemServiceManager.startService(AlarmManagerService.class);
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 4bb0902..4c75452 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -19,6 +19,7 @@
import static android.system.OsConstants.*;
import android.os.SystemClock;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkUtils;
import android.net.apf.ApfGenerator;
@@ -44,6 +45,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.Thread;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
@@ -171,8 +173,8 @@
private static final int ETH_HEADER_LEN = 14;
private static final int ETH_DEST_ADDR_OFFSET = 0;
private static final int ETH_ETHERTYPE_OFFSET = 12;
- private static final byte[] ETH_BROADCAST_MAC_ADDRESS = new byte[]{
- (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+ private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
+ {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
// TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN.
private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6;
// Endianness is not an issue for this constant because the APF interpreter always operates in
@@ -181,6 +183,7 @@
private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
private static final int IPV4_ANY_HOST_ADDRESS = 0;
+ private static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255
private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
@@ -188,7 +191,7 @@
private static final int IPV6_HEADER_LEN = 40;
// The IPv6 all nodes address ff02::1
private static final byte[] IPV6_ALL_NODES_ADDRESS =
- new byte[]{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+ { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
@@ -206,7 +209,7 @@
private static final int ARP_OPCODE_OFFSET = ARP_HEADER_OFFSET + 6;
private static final short ARP_OPCODE_REQUEST = 1;
private static final short ARP_OPCODE_REPLY = 2;
- private static final byte[] ARP_IPV4_HEADER = new byte[]{
+ private static final byte[] ARP_IPV4_HEADER = {
0, 1, // Hardware type: Ethernet (1)
8, 0, // Protocol type: IP (0x0800)
6, // Hardware size: 6
@@ -229,6 +232,9 @@
// Our IPv4 address, if we have just one, otherwise null.
@GuardedBy("this")
private byte[] mIPv4Address;
+ // The subnet prefix length of our IPv4 network. Only valid if mIPv4Address is not null.
+ @GuardedBy("this")
+ private int mIPv4PrefixLength;
@VisibleForTesting
ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
@@ -364,26 +370,6 @@
// Can't be static because it's in a non-static inner class.
// TODO: Make this static once RA is its own class.
- private int uint8(byte b) {
- return b & 0xff;
- }
-
- private int uint16(short s) {
- return s & 0xffff;
- }
-
- private long uint32(int i) {
- return i & 0xffffffffL;
- }
-
- private long getUint16(ByteBuffer buffer, int position) {
- return uint16(buffer.getShort(position));
- }
-
- private long getUint32(ByteBuffer buffer, int position) {
- return uint32(buffer.getInt(position));
- }
-
private void prefixOptionToString(StringBuffer sb, int offset) {
String prefix = IPv6AddresstoString(offset + 16);
int length = uint8(mPacket.get(offset + 2));
@@ -737,39 +723,57 @@
// Here's a basic summary of what the IPv4 filter program does:
//
// if filtering multicast (i.e. multicast lock not held):
- // if it's multicast:
- // drop
- // if it's not broadcast:
+ // if it's DHCP destined to our MAC:
// pass
- // if it's not DHCP destined to our MAC:
+ // if it's L2 broadcast:
+ // drop
+ // if it's IPv4 multicast:
+ // drop
+ // if it's IPv4 broadcast:
// drop
// pass
if (mMulticastFilter) {
- // Check for multicast destination address range
+ final String skipDhcpv4Filter = "skip_dhcp_v4_filter";
+
+ // Pass DHCP addressed to us.
+ // Check it's UDP.
+ gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
+ gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter);
+ // Check it's not a fragment. This matches the BPF filter installed by the DHCP client.
+ gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET);
+ gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipDhcpv4Filter);
+ // Check it's addressed to DHCP client port.
+ gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+ gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET);
+ gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter);
+ // Check it's DHCP to our MAC address.
+ gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
+ // NOTE: Relies on R1 containing IPv4 header offset.
+ gen.addAddR1();
+ gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter);
+ gen.addJump(gen.PASS_LABEL);
+
+ // Drop all multicasts/broadcasts.
+ gen.defineLabel(skipDhcpv4Filter);
+
+ // If IPv4 destination address is in multicast range, drop.
gen.addLoad8(Register.R0, IPV4_DEST_ADDR_OFFSET);
gen.addAnd(0xf0);
gen.addJumpIfR0Equals(0xe0, gen.DROP_LABEL);
- // Drop all broadcasts besides DHCP addressed to us
- // If not a broadcast packet, pass
+ // If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
+ gen.addLoad32(Register.R0, IPV4_DEST_ADDR_OFFSET);
+ gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, gen.DROP_LABEL);
+ if (mIPv4Address != null && mIPv4PrefixLength < 31) {
+ int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength);
+ gen.addJumpIfR0Equals(broadcastAddr, gen.DROP_LABEL);
+ }
+
+ // If L2 broadcast packet, drop.
gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, gen.PASS_LABEL);
- // If not UDP, drop
- gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
- gen.addJumpIfR0NotEquals(IPPROTO_UDP, gen.DROP_LABEL);
- // If fragment, drop. This matches the BPF filter installed by the DHCP client.
- gen.addLoad16(Register.R0, IPV4_FRAGMENT_OFFSET_OFFSET);
- gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, gen.DROP_LABEL);
- // If not to DHCP client port, drop
- gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
- gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET);
- gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, gen.DROP_LABEL);
- // If not DHCP to our MAC address, drop
- gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
- // NOTE: Relies on R1 containing IPv4 header offset.
- gen.addAddR1();
- gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, gen.DROP_LABEL);
+ gen.addJump(gen.DROP_LABEL);
}
// Otherwise, pass
@@ -1062,26 +1066,32 @@
}
}
- // Find the single IPv4 address if there is one, otherwise return null.
- private static byte[] findIPv4Address(LinkProperties lp) {
- byte[] ipv4Address = null;
- for (InetAddress inetAddr : lp.getAddresses()) {
- byte[] addr = inetAddr.getAddress();
- if (addr.length != 4) continue;
- // More than one IPv4 address, abort
- if (ipv4Address != null && !Arrays.equals(ipv4Address, addr)) return null;
- ipv4Address = addr;
+ /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */
+ private static LinkAddress findIPv4LinkAddress(LinkProperties lp) {
+ LinkAddress ipv4Address = null;
+ for (LinkAddress address : lp.getLinkAddresses()) {
+ if (!(address.getAddress() instanceof Inet4Address)) {
+ continue;
+ }
+ if (ipv4Address != null && !ipv4Address.isSameAddressAs(address)) {
+ // More than one IPv4 address, abort.
+ return null;
+ }
+ ipv4Address = address;
}
return ipv4Address;
}
public synchronized void setLinkProperties(LinkProperties lp) {
// NOTE: Do not keep a copy of LinkProperties as it would further duplicate state.
- byte[] ipv4Address = findIPv4Address(lp);
- // If ipv4Address is the same as mIPv4Address, then there's no change, just return.
- if (Arrays.equals(ipv4Address, mIPv4Address)) return;
- // Otherwise update mIPv4Address and install new program.
- mIPv4Address = ipv4Address;
+ final LinkAddress ipv4Address = findIPv4LinkAddress(lp);
+ final byte[] addr = (ipv4Address != null) ? ipv4Address.getAddress().getAddress() : null;
+ final int prefix = (ipv4Address != null) ? ipv4Address.getPrefixLength() : 0;
+ if ((prefix == mIPv4PrefixLength) && Arrays.equals(addr, mIPv4Address)) {
+ return;
+ }
+ mIPv4Address = addr;
+ mIPv4PrefixLength = prefix;
installNewProgramLocked();
}
@@ -1127,4 +1137,38 @@
pw.decreaseIndent();
}
}
+
+ private static int uint8(byte b) {
+ return b & 0xff;
+ }
+
+ private static int uint16(short s) {
+ return s & 0xffff;
+ }
+
+ private static long uint32(int i) {
+ return i & 0xffffffffL;
+ }
+
+ private static long getUint16(ByteBuffer buffer, int position) {
+ return uint16(buffer.getShort(position));
+ }
+
+ private static long getUint32(ByteBuffer buffer, int position) {
+ return uint32(buffer.getInt(position));
+ }
+
+ // TODO: move to android.net.NetworkUtils
+ @VisibleForTesting
+ public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) {
+ return bytesToInt(addrBytes) | (int) (uint32(-1) >>> prefixLength);
+ }
+
+ @VisibleForTesting
+ public static int bytesToInt(byte[] addrBytes) {
+ return (uint8(addrBytes[0]) << 24)
+ + (uint8(addrBytes[1]) << 16)
+ + (uint8(addrBytes[2]) << 8)
+ + (uint8(addrBytes[3]));
+ }
}
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 811c0e6..97d8bdd 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -22,6 +22,7 @@
guava \
android-support-test \
mockito-target-minus-junit4 \
+ platform-test-annotations \
ShortcutManagerTestUtils
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/services/tests/servicestests/src/android/net/apf/ApfTest.java b/services/tests/servicestests/src/android/net/apf/ApfTest.java
index bd76118..f7c61d1 100644
--- a/services/tests/servicestests/src/android/net/apf/ApfTest.java
+++ b/services/tests/servicestests/src/android/net/apf/ApfTest.java
@@ -20,6 +20,9 @@
import com.android.frameworks.servicestests.R;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkUtils;
import android.net.apf.ApfCapabilities;
import android.net.apf.ApfFilter;
import android.net.apf.ApfGenerator;
@@ -28,8 +31,6 @@
import android.net.ip.IpManager;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.RaEvent;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
import android.os.ConditionVariable;
import android.os.Parcelable;
import android.system.ErrnoException;
@@ -61,7 +62,7 @@
* Tests for APF program generator and interpreter.
*
* Build, install and run with:
- * runtest frameworks-services -c com.android.server.ApfTest
+ * runtest frameworks-services -c android.net.apf.ApfTest
*/
public class ApfTest extends AndroidTestCase {
private static final int TIMEOUT_MS = 500;
@@ -86,21 +87,45 @@
private final static boolean DROP_MULTICAST = true;
private final static boolean ALLOW_MULTICAST = false;
+ private static String label(int code) {
+ switch (code) {
+ case PASS: return "PASS";
+ case DROP: return "DROP";
+ default: return "UNKNOWN";
+ }
+ }
+
+ private static void assertReturnCodesEqual(int expected, int got) {
+ assertEquals(label(expected), label(got));
+ }
+
private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
- assertEquals(expected, apfSimulate(program, packet, filterAge));
+ assertReturnCodesEqual(expected, apfSimulate(program, packet, filterAge));
+ }
+
+ private void assertVerdict(int expected, byte[] program, byte[] packet) {
+ assertReturnCodesEqual(expected, apfSimulate(program, packet, 0));
}
private void assertPass(byte[] program, byte[] packet, int filterAge) {
assertVerdict(PASS, program, packet, filterAge);
}
+ private void assertPass(byte[] program, byte[] packet) {
+ assertVerdict(PASS, program, packet);
+ }
+
private void assertDrop(byte[] program, byte[] packet, int filterAge) {
assertVerdict(DROP, program, packet, filterAge);
}
+ private void assertDrop(byte[] program, byte[] packet) {
+ assertVerdict(DROP, program, packet);
+ }
+
private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
throws IllegalInstructionException {
- assertEquals(expected, apfSimulate(gen.generate(), packet, filterAge));
+ assertReturnCodesEqual(expected, apfSimulate(gen.generate(), packet, filterAge));
}
private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
@@ -516,7 +541,7 @@
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1);
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
- byte[] packet123 = new byte[]{0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ byte[] packet123 = {0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
assertPass(gen, packet123, 0);
gen = new ApfGenerator();
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
@@ -524,7 +549,7 @@
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1);
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
- byte[] packet12345 = new byte[]{0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
+ byte[] packet12345 = {0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
assertDrop(gen, packet12345, 0);
gen = new ApfGenerator();
gen.addLoadImmediate(Register.R0, 1);
@@ -575,12 +600,12 @@
}
private static class TestApfFilter extends ApfFilter {
- public final static byte[] MOCK_MAC_ADDR = new byte[]{1,2,3,4,5,6};
+ public final static byte[] MOCK_MAC_ADDR = {1,2,3,4,5,6};
private FileDescriptor mWriteSocket;
public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter,
IpConnectivityLog log) throws Exception {
- super(new ApfCapabilities(2, 1536, ARPHRD_ETHER), NetworkInterface.getByName("lo"),
+ super(new ApfCapabilities(2, 1700, ARPHRD_ETHER), NetworkInterface.getByName("lo"),
ipManagerCallback, multicastFilter, log);
}
@@ -620,19 +645,21 @@
private static final int ETH_HEADER_LEN = 14;
private static final int ETH_DEST_ADDR_OFFSET = 0;
private static final int ETH_ETHERTYPE_OFFSET = 12;
- private static final byte[] ETH_BROADCAST_MAC_ADDRESS = new byte[]{
- (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+ private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
+ {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0;
private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
+ private static final byte[] IPV4_BROADCAST_ADDRESS =
+ {(byte) 255, (byte) 255, (byte) 255, (byte) 255};
private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
private static final int IPV6_HEADER_LEN = 40;
private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
// The IPv6 all nodes address ff02::1
private static final byte[] IPV6_ALL_NODES_ADDRESS =
- new byte[]{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+ { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
@@ -670,14 +697,14 @@
private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;
private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
- private static final byte[] ARP_IPV4_REQUEST_HEADER = new byte[]{
+ private static final byte[] ARP_IPV4_REQUEST_HEADER = {
0, 1, // Hardware type: Ethernet (1)
8, 0, // Protocol type: IP (0x0800)
6, // Hardware size: 6
4, // Protocol size: 4
0, 1 // Opcode: request (1)
};
- private static final byte[] ARP_IPV4_REPLY_HEADER = new byte[]{
+ private static final byte[] ARP_IPV4_REPLY_HEADER = {
0, 1, // Hardware type: Ethernet (1)
8, 0, // Protocol type: IP (0x0800)
6, // Hardware size: 6
@@ -686,38 +713,63 @@
};
private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;
- private static final byte[] MOCK_IPV4_ADDR = new byte[]{10, 0, 0, 1};
- private static final byte[] ANOTHER_IPV4_ADDR = new byte[]{10, 0, 0, 2};
- private static final byte[] IPV4_ANY_HOST_ADDR = new byte[]{0, 0, 0, 0};
+ private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1};
+ private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19
+ private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1};
+ private static final byte[] ANOTHER_IPV4_ADDR = {10, 0, 0, 2};
+ private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0};
@LargeTest
public void testApfFilterIPv4() throws Exception {
MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog);
+ apfFilter.setLinkProperties(lp);
+
byte[] program = ipManagerCallback.getApfProgram();
// Verify empty packet of 100 zero bytes is passed
ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
- assertPass(program, packet.array(), 0);
+ assertPass(program, packet.array());
// Verify unicast IPv4 packet is passed
+ put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- assertPass(program, packet.array(), 0);
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR);
+ assertPass(program, packet.array());
- // Verify broadcast IPv4, not DHCP to us, is dropped
- packet.put(ETH_BROADCAST_MAC_ADDRESS);
- assertDrop(program, packet.array(), 0);
+ // Verify L2 unicast to IPv4 broadcast addresses is dropped (b/30231088)
+ put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+
+ // Verify multicast/broadcast IPv4, not DHCP to us, is dropped
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ assertDrop(program, packet.array());
packet.put(IPV4_VERSION_IHL_OFFSET, (byte)0x45);
- assertDrop(program, packet.array(), 0);
+ assertDrop(program, packet.array());
packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
- assertDrop(program, packet.array(), 0);
+ assertDrop(program, packet.array());
packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT);
- assertDrop(program, packet.array(), 0);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_MULTICAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+ assertDrop(program, packet.array());
// Verify broadcast IPv4 DHCP to us is passed
- packet.position(DHCP_CLIENT_MAC_OFFSET);
- packet.put(TestApfFilter.MOCK_MAC_ADDR);
- assertPass(program, packet.array(), 0);
+ put(packet, DHCP_CLIENT_MAC_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+ assertPass(program, packet.array());
+
+ // Verify unicast IPv4 DHCP to us is passed
+ put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+ assertPass(program, packet.array());
apfFilter.shutdown();
}
@@ -731,82 +783,108 @@
// Verify empty IPv6 packet is passed
ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
- assertPass(program, packet.array(), 0);
+ assertPass(program, packet.array());
// Verify empty ICMPv6 packet is passed
packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
- assertPass(program, packet.array(), 0);
+ assertPass(program, packet.array());
// Verify empty ICMPv6 NA packet is passed
packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT);
- assertPass(program, packet.array(), 0);
+ assertPass(program, packet.array());
// Verify ICMPv6 NA to ff02::1 is dropped
- packet.position(IPV6_DEST_ADDR_OFFSET);
- packet.put(IPV6_ALL_NODES_ADDRESS);
- assertDrop(program, packet.array(), 0);
+ put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS);
+ assertDrop(program, packet.array());
apfFilter.shutdown();
}
@LargeTest
public void testApfFilterMulticast() throws Exception {
+ final byte[] unicastIpv4Addr = {(byte)192,0,2,63};
+ final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
+ final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
+ final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
+
MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
+ apfFilter.setLinkProperties(lp);
+
byte[] program = ipManagerCallback.getApfProgram();
// Construct IPv4 and IPv6 multicast packets.
ByteBuffer mcastv4packet = ByteBuffer.wrap(new byte[100]);
mcastv4packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- mcastv4packet.position(IPV4_DEST_ADDR_OFFSET);
- mcastv4packet.put(new byte[]{(byte)224,0,0,1});
+ put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
ByteBuffer mcastv6packet = ByteBuffer.wrap(new byte[100]);
mcastv6packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_UDP);
- mcastv6packet.position(IPV6_DEST_ADDR_OFFSET);
- mcastv6packet.put(new byte[]{(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb});
+ put(mcastv6packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
// Construct IPv4 broadcast packet.
- ByteBuffer bcastv4packet = ByteBuffer.wrap(new byte[100]);
- bcastv4packet.put(ETH_BROADCAST_MAC_ADDRESS);
- bcastv4packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
- bcastv4packet.position(IPV4_DEST_ADDR_OFFSET);
- bcastv4packet.put(new byte[]{(byte)192,(byte)0,(byte)2,(byte)63});
+ ByteBuffer bcastv4packet1 = ByteBuffer.wrap(new byte[100]);
+ bcastv4packet1.put(ETH_BROADCAST_MAC_ADDRESS);
+ bcastv4packet1.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4packet1, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
+
+ ByteBuffer bcastv4packet2 = ByteBuffer.wrap(new byte[100]);
+ bcastv4packet2.put(ETH_BROADCAST_MAC_ADDRESS);
+ bcastv4packet2.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4packet2, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+
+ // Construct IPv4 broadcast with L2 unicast address packet (b/30231088).
+ ByteBuffer bcastv4unicastl2packet = ByteBuffer.wrap(new byte[100]);
+ bcastv4unicastl2packet.put(TestApfFilter.MOCK_MAC_ADDR);
+ bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr);
// Verify initially disabled multicast filter is off
- assertPass(program, bcastv4packet.array(), 0);
- assertPass(program, mcastv4packet.array(), 0);
- assertPass(program, mcastv6packet.array(), 0);
+ assertPass(program, mcastv4packet.array());
+ assertPass(program, mcastv6packet.array());
+ assertPass(program, bcastv4packet1.array());
+ assertPass(program, bcastv4packet2.array());
+ assertPass(program, bcastv4unicastl2packet.array());
// Turn on multicast filter and verify it works
ipManagerCallback.resetApfProgramWait();
apfFilter.setMulticastFilter(true);
program = ipManagerCallback.getApfProgram();
- assertDrop(program, bcastv4packet.array(), 0);
- assertDrop(program, mcastv4packet.array(), 0);
- assertDrop(program, mcastv6packet.array(), 0);
+ assertDrop(program, mcastv4packet.array());
+ assertDrop(program, mcastv6packet.array());
+ assertDrop(program, bcastv4packet1.array());
+ assertDrop(program, bcastv4packet2.array());
+ assertDrop(program, bcastv4unicastl2packet.array());
// Turn off multicast filter and verify it's off
ipManagerCallback.resetApfProgramWait();
apfFilter.setMulticastFilter(false);
program = ipManagerCallback.getApfProgram();
- assertPass(program, bcastv4packet.array(), 0);
- assertPass(program, mcastv4packet.array(), 0);
- assertPass(program, mcastv6packet.array(), 0);
+ assertPass(program, mcastv4packet.array());
+ assertPass(program, mcastv6packet.array());
+ assertPass(program, bcastv4packet1.array());
+ assertPass(program, bcastv4packet2.array());
+ assertPass(program, bcastv4unicastl2packet.array());
// Verify it can be initialized to on
ipManagerCallback.resetApfProgramWait();
apfFilter.shutdown();
apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog);
+ apfFilter.setLinkProperties(lp);
program = ipManagerCallback.getApfProgram();
- assertDrop(program, bcastv4packet.array(), 0);
- assertDrop(program, mcastv4packet.array(), 0);
- assertDrop(program, mcastv6packet.array(), 0);
+ assertDrop(program, mcastv4packet.array());
+ assertDrop(program, mcastv6packet.array());
+ assertDrop(program, bcastv4packet1.array());
+ assertDrop(program, bcastv4unicastl2packet.array());
// Verify that ICMPv6 multicast is not dropped.
mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
- assertPass(program, mcastv6packet.array(), 0);
+ assertPass(program, mcastv6packet.array());
apfFilter.shutdown();
}
@@ -819,17 +897,17 @@
private void verifyArpFilter(byte[] program, int filterResult) {
// Verify ARP request packet
- assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR), 0);
- assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR), 0);
- assertDrop(program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR), 0);
+ assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR));
+ assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR));
+ assertDrop(program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR));
// Verify unicast ARP reply packet is always accepted.
- assertPass(program, arpReplyUnicast(MOCK_IPV4_ADDR), 0);
- assertPass(program, arpReplyUnicast(ANOTHER_IPV4_ADDR), 0);
- assertPass(program, arpReplyUnicast(IPV4_ANY_HOST_ADDR), 0);
+ assertPass(program, arpReplyUnicast(MOCK_IPV4_ADDR));
+ assertPass(program, arpReplyUnicast(ANOTHER_IPV4_ADDR));
+ assertPass(program, arpReplyUnicast(IPV4_ANY_HOST_ADDR));
// Verify GARP reply packets are always filtered
- assertDrop(program, garpReply(), 0);
+ assertDrop(program, garpReply());
}
@LargeTest
@@ -855,34 +933,26 @@
private static byte[] arpRequestBroadcast(byte[] tip) {
ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
- packet.position(ETH_DEST_ADDR_OFFSET);
- packet.put(ETH_BROADCAST_MAC_ADDRESS);
- packet.position(ARP_HEADER_OFFSET);
- packet.put(ARP_IPV4_REQUEST_HEADER);
- packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
- packet.put(tip);
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
return packet.array();
}
private static byte[] arpReplyUnicast(byte[] tip) {
ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
- packet.position(ARP_HEADER_OFFSET);
- packet.put(ARP_IPV4_REPLY_HEADER);
- packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
- packet.put(tip);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
return packet.array();
}
private static byte[] garpReply() {
ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
- packet.position(ETH_DEST_ADDR_OFFSET);
- packet.put(ETH_BROADCAST_MAC_ADDRESS);
- packet.position(ARP_HEADER_OFFSET);
- packet.put(ARP_IPV4_REPLY_HEADER);
- packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
- packet.put(IPV4_ANY_HOST_ADDR);
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, IPV4_ANY_HOST_ADDR);
return packet.array();
}
@@ -893,22 +963,22 @@
byte[] program = ipManagerCallback.getApfProgram();
// Verify new program should drop RA for 1/6th its lifetime
- assertDrop(program, packet.array(), 0);
+ assertDrop(program, packet.array());
assertDrop(program, packet.array(), lifetime/6);
assertPass(program, packet.array(), lifetime/6 + 1);
assertPass(program, packet.array(), lifetime);
// Verify RA checksum is ignored
packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
- assertDrop(program, packet.array(), 0);
+ assertDrop(program, packet.array());
packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
- assertDrop(program, packet.array(), 0);
+ assertDrop(program, packet.array());
// Verify other changes to RA make it not match filter
packet.put(0, (byte)-1);
- assertPass(program, packet.array(), 0);
+ assertPass(program, packet.array());
packet.put(0, (byte)0);
- assertDrop(program, packet.array(), 0);
+ assertDrop(program, packet.array());
}
// Test that when ApfFilter is shown the given packet, it generates a program to filter it
@@ -973,7 +1043,7 @@
basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)1000);
basePacket.position(IPV6_DEST_ADDR_OFFSET);
basePacket.put(IPV6_ALL_NODES_ADDRESS);
- assertPass(program, basePacket.array(), 0);
+ assertPass(program, basePacket.array());
testRaLifetime(apfFilter, ipManagerCallback, basePacket, 1000);
verifyRaEvent(new RaEvent(1000, -1, -1, -1, -1, -1));
@@ -1069,6 +1139,13 @@
return file.getAbsolutePath();
}
+ private static void put(ByteBuffer buffer, int position, byte[] bytes) {
+ final int original = buffer.position();
+ buffer.position(position);
+ buffer.put(bytes);
+ buffer.position(original);
+ }
+
/**
* Call the APF interpreter the run {@code program} on {@code packet} pretending the
* filter was installed {@code filter_age} seconds ago.
@@ -1089,4 +1166,30 @@
*/
private native static boolean compareBpfApf(String filter, String pcap_filename,
byte[] apf_program);
+
+ public void testBytesToInt() {
+ assertEquals(0x00000000, ApfFilter.bytesToInt(IPV4_ANY_HOST_ADDR));
+ assertEquals(0xffffffff, ApfFilter.bytesToInt(IPV4_BROADCAST_ADDRESS));
+ assertEquals(0x0a000001, ApfFilter.bytesToInt(MOCK_IPV4_ADDR));
+ assertEquals(0x0a000002, ApfFilter.bytesToInt(ANOTHER_IPV4_ADDR));
+ assertEquals(0x0a001fff, ApfFilter.bytesToInt(MOCK_BROADCAST_IPV4_ADDR));
+ assertEquals(0xe0000001, ApfFilter.bytesToInt(MOCK_MULTICAST_IPV4_ADDR));
+ }
+
+ public void testBroadcastAddress() throws Exception {
+ assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
+ assertEqualsIp("0.0.0.0", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 32));
+ assertEqualsIp("0.0.3.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 22));
+ assertEqualsIp("0.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 8));
+
+ assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 0));
+ assertEqualsIp("10.0.0.1", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 32));
+ assertEqualsIp("10.0.0.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 24));
+ assertEqualsIp("10.0.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 16));
+ }
+
+ public void assertEqualsIp(String expected, int got) throws Exception {
+ int want = ApfFilter.bytesToInt(InetAddress.getByName(expected).getAddress());
+ assertEquals(want, got);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index be1642e..de90de8 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.mock;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -137,7 +138,8 @@
@Override
public Object getSystemService(String name) {
- if (name == Context.CONNECTIVITY_SERVICE) return mCm;
+ if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
+ if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
return super.getSystemService(name);
}
@@ -717,6 +719,7 @@
}
public void tearDown() throws Exception {
+ setMobileDataAlwaysOn(false);
if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); }
if (mWiFiNetworkAgent != null) { mWiFiNetworkAgent.disconnect(); }
mCellNetworkAgent = mWiFiNetworkAgent = null;
@@ -1820,6 +1823,85 @@
mCm.unregisterNetworkCallback(cellNetworkCallback);
}
+ private void setMobileDataAlwaysOn(boolean enable) {
+ ContentResolver cr = mServiceContext.getContentResolver();
+ Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0);
+ mService.updateMobileDataAlwaysOn();
+ mService.waitForIdle();
+ }
+
+ private boolean isForegroundNetwork(MockNetworkAgent network) {
+ NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork());
+ assertNotNull(nc);
+ return nc.hasCapability(NET_CAPABILITY_FOREGROUND);
+ }
+
+ @SmallTest
+ public void testBackgroundNetworks() throws Exception {
+ // Create a background request. We can't do this ourselves because ConnectivityService
+ // doesn't have an API for it. So just turn on mobile data always on.
+ setMobileDataAlwaysOn(true);
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ final NetworkRequest fgRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_FOREGROUND).build();
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ final TestNetworkCallback fgCallback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(request, callback);
+ mCm.registerNetworkCallback(fgRequest, fgCallback);
+
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ assertTrue(isForegroundNetwork(mCellNetworkAgent));
+
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+
+ // When wifi connects, cell lingers.
+ callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ fgCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ assertTrue(isForegroundNetwork(mCellNetworkAgent));
+ assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
+
+ // When lingering is complete, cell is still there but is now in the background.
+ fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, TEST_LINGER_DELAY_MS);
+ callback.assertNoCallback();
+ assertFalse(isForegroundNetwork(mCellNetworkAgent));
+ assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
+
+ // File a cell request and check that cell comes into the foreground.
+ final NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ final TestNetworkCallback cellCallback = new TestNetworkCallback();
+ mCm.requestNetwork(cellRequest, cellCallback);
+ cellCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ callback.assertNoCallback(); // Because the network is already up.
+ assertTrue(isForegroundNetwork(mCellNetworkAgent));
+ assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
+
+ // Release the request. The network immediately goes into the background, since it was not
+ // lingering.
+ mCm.unregisterNetworkCallback(cellCallback);
+ fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ callback.assertNoCallback();
+ assertFalse(isForegroundNetwork(mCellNetworkAgent));
+ assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
+
+ // Disconnect wifi and check that cell is foreground again.
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ assertTrue(isForegroundNetwork(mCellNetworkAgent));
+
+ mCm.unregisterNetworkCallback(callback);
+ mCm.unregisterNetworkCallback(fgCallback);
+ }
+
@SmallTest
public void testRequestBenchmark() throws Exception {
// Benchmarks connecting and switching performance in the presence of a large number of
@@ -1925,8 +2007,7 @@
// Turn on mobile data always on. The factory starts looking again.
testFactory.expectAddRequests(1);
- Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, 1);
- mService.updateMobileDataAlwaysOn();
+ setMobileDataAlwaysOn(true);
testFactory.waitForNetworkRequests(2);
assertTrue(testFactory.getMyStartRequested());
@@ -1946,8 +2027,7 @@
// Turn off mobile data always on and expect the request to disappear...
testFactory.expectRemoveRequests(1);
- Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, 0);
- mService.updateMobileDataAlwaysOn();
+ setMobileDataAlwaysOn(false);
testFactory.waitForNetworkRequests(1);
// ... and cell data to be torn down.
@@ -1959,6 +2039,79 @@
handlerThread.quit();
}
+ @SmallTest
+ public void testAvoidBadWifiSetting() throws Exception {
+ ContentResolver cr = mServiceContext.getContentResolver();
+
+ // File a request for cell to ensure it doesn't go down.
+ final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ mCm.requestNetwork(cellRequest, cellNetworkCallback);
+
+ TestNetworkCallback defaultCallback = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallback(defaultCallback);
+
+ NetworkRequest validatedWifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_VALIDATED)
+ .build();
+ TestNetworkCallback validatedWifiCallback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback);
+
+ // Takes effect on every rematch.
+ Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 0);
+
+ // Bring up validated cell.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ Network cellNetwork = mCellNetworkAgent.getNetwork();
+
+ // Bring up validated wifi.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ validatedWifiCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
+
+ // Fail validation on wifi.
+ mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+ mCm.reportNetworkConnectivity(wifiNetwork, false);
+ validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+ // Because avoid bad wifi is off, we don't switch to cellular.
+ defaultCallback.assertNoCallback();
+ assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ assertEquals(mCm.getActiveNetwork(), wifiNetwork);
+
+ // Simulate the user selecting "switch" on the dialog.
+ Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
+ mService.updateNetworkAvoidBadWifi();
+
+ // We now switch to cell.
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ assertEquals(mCm.getActiveNetwork(), cellNetwork);
+
+ // If cell goes down, we switch to wifi.
+ mCellNetworkAgent.disconnect();
+ defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ validatedWifiCallback.assertNoCallback();
+
+ mCm.unregisterNetworkCallback(cellNetworkCallback);
+ mCm.unregisterNetworkCallback(validatedWifiCallback);
+ mCm.unregisterNetworkCallback(defaultCallback);
+ }
+
/**
* Validate that a satisfied network request does not trigger onUnavailable() once the
* time-out period expires.
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
index 42d3b07..9ccf290 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
@@ -41,6 +41,7 @@
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
@RunWith(AndroidJUnit4.class)
@@ -55,12 +56,16 @@
AccessibilityCache mAccessibilityCache;
AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher;
+ AtomicInteger numA11yNodeInfosInUse = new AtomicInteger(0);
+ AtomicInteger numA11yWinInfosInUse = new AtomicInteger(0);
@Before
public void setUp() {
mAccessibilityNodeRefresher = mock(AccessibilityCache.AccessibilityNodeRefresher.class);
when(mAccessibilityNodeRefresher.refreshNode(anyObject(), anyBoolean())).thenReturn(true);
mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher);
+ AccessibilityNodeInfo.setNumInstancesInUseCounter(numA11yNodeInfosInUse);
+ AccessibilityWindowInfo.setNumInstancesInUseCounter(numA11yWinInfosInUse);
}
@After
@@ -68,8 +73,8 @@
// Make sure we're recycling all of our window and node infos
mAccessibilityCache.clear();
AccessibilityInteractionClient.getInstance().clearCache();
- assertEquals(0, AccessibilityWindowInfo.getNumInstancesInUse());
- assertEquals(0, AccessibilityNodeInfo.getNumInstancesInUse());
+ assertEquals(0, numA11yWinInfosInUse.get());
+ assertEquals(0, numA11yNodeInfosInUse.get());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java b/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java
index 7571f79..984a484 100644
--- a/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java
@@ -16,36 +16,19 @@
package com.android.server.am;
-import android.app.ActivityManager;
-import android.content.ContentResolver;
-import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.os.Environment;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import android.test.AndroidTestCase;
import android.util.Log;
import android.util.SparseBooleanArray;
-import android.util.Xml;
-import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
import com.android.server.am.TaskPersister;
import java.io.File;
-import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
import java.util.Random;
-import libcore.io.IoUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
public class TaskPersisterTest extends AndroidTestCase {
private static final String TEST_USER_NAME = "AM-Test-User";
@@ -86,140 +69,6 @@
taskIdsOnFile.equals(newTaskIdsOnFile));
}
- public void testActiveTimeMigration() {
- // Simulate a migration scenario by setting the last write uptime to zero
- ContentResolver cr = getContext().getContentResolver();
- Settings.Secure.putLong(cr,
- Settings.Secure.TASK_PERSISTER_LAST_WRITE_UPTIME, 0);
-
- // Create a dummy task record with an absolute time 1s before now
- long pastOffset = 1000;
- long activeTime = System.currentTimeMillis() - pastOffset;
- TaskRecord tr0 = createDummyTaskRecordWithActiveTime(activeTime, activeTime);
-
- // Save and load the tasks with no last persist uptime (0)
- String tr0XmlStr = serializeTaskRecordToXmlString(tr0);
- TaskRecord xtr0 = unserializeTaskRecordFromXmlString(tr0XmlStr, 0);
-
- // Ensure that the absolute time has been migrated to be relative to the current elapsed
- // time
- assertTrue("Expected firstActiveTime to be migrated from: " + tr0.firstActiveTime +
- " instead found: " + xtr0.firstActiveTime,
- xtr0.firstActiveTime <= -pastOffset);
- assertTrue("Expected lastActiveTime to be migrated from: " + tr0.lastActiveTime +
- " instead found: " + xtr0.lastActiveTime,
- xtr0.lastActiveTime <= -pastOffset);
-
- // Ensure that the last active uptime is not set so that SystemUI can migrate it itself
- // assuming that the last persist time is zero
- Settings.Secure.putLongForUser(cr,
- Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME, 0, testUserId);
- mTaskPersister.restoreTasksForUserLocked(testUserId);
- long lastVisTaskActiveTime = Settings.Secure.getLongForUser(cr,
- Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME, -1, testUserId);
- assertTrue("Expected last visible task active time is zero", lastVisTaskActiveTime == 0);
- }
-
- public void testActiveTimeOffsets() {
- // Simulate a normal boot scenario by setting the last write uptime
- long lastWritePastOffset = 1000;
- long lastVisActivePastOffset = 500;
- ContentResolver cr = getContext().getContentResolver();
- Settings.Secure.putLong(cr,
- Settings.Secure.TASK_PERSISTER_LAST_WRITE_UPTIME, lastWritePastOffset);
-
- // Create a dummy task record with an absolute time 1s before now
- long activeTime = 250;
- TaskRecord tr0 = createDummyTaskRecordWithActiveTime(activeTime, activeTime);
-
- // Save and load the tasks with the last persist time
- String tr0XmlStr = serializeTaskRecordToXmlString(tr0);
- TaskRecord xtr0 = unserializeTaskRecordFromXmlString(tr0XmlStr, lastWritePastOffset);
-
- // Ensure that the prior elapsed time has been offset to be relative to the current boot
- // time
- assertTrue("Expected firstActiveTime to be offset from: " + tr0.firstActiveTime +
- " instead found: " + xtr0.firstActiveTime,
- xtr0.firstActiveTime <= (-lastWritePastOffset + activeTime));
- assertTrue("Expected lastActiveTime to be offset from: " + tr0.lastActiveTime +
- " instead found: " + xtr0.lastActiveTime,
- xtr0.lastActiveTime <= (-lastWritePastOffset + activeTime));
-
- // Ensure that we update the last active uptime as well by simulating a restoreTasks call
- Settings.Secure.putLongForUser(cr,
- Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME, lastVisActivePastOffset,
- testUserId);
- mTaskPersister.restoreTasksForUserLocked(testUserId);
- long lastVisTaskActiveTime = Settings.Secure.getLongForUser(cr,
- Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME, Long.MAX_VALUE,
- testUserId);
- assertTrue("Expected last visible task active time to be offset", lastVisTaskActiveTime <=
- (-lastWritePastOffset + lastVisActivePastOffset));
- }
-
- private TaskRecord createDummyTaskRecordWithActiveTime(long firstActiveTime,
- long lastActiveTime) {
- ActivityInfo info = createDummyActivityInfo();
- ActivityManager.TaskDescription td = new ActivityManager.TaskDescription();
- TaskRecord t = new TaskRecord(null, 0, info, null, td, null);
- t.firstActiveTime = firstActiveTime;
- t.lastActiveTime = lastActiveTime;
- return t;
- }
-
- private ActivityInfo createDummyActivityInfo() {
- ActivityInfo info = new ActivityInfo();
- info.applicationInfo = getContext().getApplicationInfo();
- return info;
- }
-
- private String serializeTaskRecordToXmlString(TaskRecord tr) {
- StringWriter stringWriter = new StringWriter();
-
- try {
- final XmlSerializer xmlSerializer = new FastXmlSerializer();
- xmlSerializer.setOutput(stringWriter);
-
- xmlSerializer.startDocument(null, true);
- xmlSerializer.startTag(null, TaskPersister.TAG_TASK);
- tr.saveToXml(xmlSerializer);
- xmlSerializer.endTag(null, TaskPersister.TAG_TASK);
- xmlSerializer.endDocument();
- xmlSerializer.flush();
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- return stringWriter.toString();
- }
-
- private TaskRecord unserializeTaskRecordFromXmlString(String xmlStr, long lastPersistUptime) {
- StringReader reader = null;
- TaskRecord task = null;
- try {
- reader = new StringReader(xmlStr);
- final XmlPullParser in = Xml.newPullParser();
- in.setInput(reader);
-
- int event;
- while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
- event != XmlPullParser.END_TAG) {
- final String name = in.getName();
- if (event == XmlPullParser.START_TAG) {
- if (TaskPersister.TAG_TASK.equals(name)) {
- task = TaskRecord.restoreFromXml(in, null, null, lastPersistUptime);
- }
- }
- XmlUtils.skipCurrentTag(in);
- }
- } catch (Exception e) {
- return null;
- } finally {
- IoUtils.closeQuietly(reader);
- }
- return task;
- }
-
private int createUser(String name, int flags) {
UserInfo user = mUserManager.createUser(name, flags);
if (user == null) {
diff --git a/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index f1f708c..5fe000a 100644
--- a/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -15,6 +15,9 @@
*/
package com.android.server.notification;
+import com.android.server.lights.Light;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -22,7 +25,10 @@
import android.app.ActivityManager;
import android.app.Notification;
import android.app.Notification.Builder;
+import android.app.NotificationManager;
import android.content.Context;
+import android.app.NotificationChannel;
+import android.graphics.Color;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.net.Uri;
@@ -30,6 +36,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.provider.Settings;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
@@ -57,6 +64,8 @@
@Mock AudioManager mAudioManager;
@Mock Vibrator mVibrator;
@Mock android.media.IRingtonePlayer mRingtonePlayer;
+ @Mock StatusBarManagerInternal mStatusBar;
+ @Mock Light mLight;
@Mock Handler mHandler;
private NotificationManagerService mService;
@@ -69,6 +78,15 @@
private int mScore = 10;
private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+ private static final long[] CUSTOM_VIBRATION = new long[] {
+ 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
+ 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
+ 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400 };
+ private static final Uri CUSTOM_SOUND = Settings.System.DEFAULT_ALARM_ALERT_URI;
+ private static final int CUSTOM_LIGHT_COLOR = Color.BLACK;
+ private static final int CUSTOM_LIGHT_ON = 10000;
+ private static final int CUSTOM_LIGHT_OFF = 10000;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -83,6 +101,9 @@
mService.setVibrator(mVibrator);
mService.setSystemReady(true);
mService.setHandler(mHandler);
+ mService.setStatusBarManager(mStatusBar);
+ mService.setLights(mLight);
+ mService.setScreenOn(false);
mService.setSystemNotificationSound("beep!");
}
@@ -92,56 +113,85 @@
private NotificationRecord getNoisyOtherNotification() {
return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
- true /* noisy */, true /* buzzy*/);
+ true /* noisy */, true /* buzzy*/, false /* lights */);
}
private NotificationRecord getBeepyNotification() {
return getNotificationRecord(mId, false /* insistent */, false /* once */,
- true /* noisy */, false /* buzzy*/);
+ true /* noisy */, false /* buzzy*/, false /* lights */);
}
private NotificationRecord getBeepyOnceNotification() {
return getNotificationRecord(mId, false /* insistent */, true /* once */,
- true /* noisy */, false /* buzzy*/);
+ true /* noisy */, false /* buzzy*/, false /* lights */);
}
private NotificationRecord getQuietNotification() {
return getNotificationRecord(mId, false /* insistent */, false /* once */,
- false /* noisy */, false /* buzzy*/);
+ false /* noisy */, false /* buzzy*/, false /* lights */);
}
private NotificationRecord getQuietOtherNotification() {
return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
- false /* noisy */, false /* buzzy*/);
+ false /* noisy */, false /* buzzy*/, false /* lights */);
}
private NotificationRecord getQuietOnceNotification() {
return getNotificationRecord(mId, false /* insistent */, true /* once */,
- false /* noisy */, false /* buzzy*/);
+ false /* noisy */, false /* buzzy*/, false /* lights */);
}
private NotificationRecord getInsistentBeepyNotification() {
return getNotificationRecord(mId, true /* insistent */, false /* once */,
- true /* noisy */, false /* buzzy*/);
+ true /* noisy */, false /* buzzy*/, false /* lights */);
}
private NotificationRecord getBuzzyNotification() {
return getNotificationRecord(mId, false /* insistent */, false /* once */,
- false /* noisy */, true /* buzzy*/);
+ false /* noisy */, true /* buzzy*/, false /* lights */);
}
private NotificationRecord getBuzzyOnceNotification() {
return getNotificationRecord(mId, false /* insistent */, true /* once */,
- false /* noisy */, true /* buzzy*/);
+ false /* noisy */, true /* buzzy*/, false /* lights */);
}
private NotificationRecord getInsistentBuzzyNotification() {
return getNotificationRecord(mId, true /* insistent */, false /* once */,
- false /* noisy */, true /* buzzy*/);
+ false /* noisy */, true /* buzzy*/, false /* lights */);
+ }
+
+ private NotificationRecord getLightsNotification() {
+ return getNotificationRecord(mId, false /* insistent */, true /* once */,
+ false /* noisy */, true /* buzzy*/, true /* lights */);
+ }
+
+ private NotificationRecord getCustomBuzzyOnceNotification() {
+ return getNotificationRecord(mId, false /* insistent */, true /* once */,
+ false /* noisy */, true /* buzzy*/, false /* lights */,
+ false /* defaultVibration */, true /* defaultSound */, true /* defaultLights */);
+ }
+
+ private NotificationRecord getCustomBeepyNotification() {
+ return getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */,
+ true /* defaultVibration */, false /* defaultSound */, true /* defaultLights */);
+ }
+
+ private NotificationRecord getCustomLightsNotification() {
+ return getNotificationRecord(mId, false /* insistent */, true /* once */,
+ false /* noisy */, true /* buzzy*/, true /* lights */,
+ true /* defaultVibration */, true /* defaultSound */, false /* defaultLights */);
}
private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
- boolean noisy, boolean buzzy) {
+ boolean noisy, boolean buzzy, boolean lights) {
+ return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true);
+ }
+
+ private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
+ boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
+ boolean defaultSound, boolean defaultLights) {
final Builder builder = new Builder(getContext())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -150,10 +200,25 @@
int defaults = 0;
if (noisy) {
- defaults |= Notification.DEFAULT_SOUND;
+ if (defaultSound) {
+ defaults |= Notification.DEFAULT_SOUND;
+ } else {
+ builder.setSound(CUSTOM_SOUND);
+ }
}
if (buzzy) {
- defaults |= Notification.DEFAULT_VIBRATE;
+ if (defaultVibration) {
+ defaults |= Notification.DEFAULT_VIBRATE;
+ } else {
+ builder.setVibrate(CUSTOM_VIBRATION);
+ }
+ }
+ if (lights) {
+ if (defaultLights) {
+ defaults |= Notification.DEFAULT_LIGHTS;
+ } else {
+ builder.setLights(CUSTOM_LIGHT_COLOR, CUSTOM_LIGHT_ON, CUSTOM_LIGHT_OFF);
+ }
}
builder.setDefaults(defaults);
@@ -163,7 +228,10 @@
}
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, mPid,
mScore, n, mUser, System.currentTimeMillis());
- return new NotificationRecord(getContext(), sbn);
+ NotificationRecord r = new NotificationRecord(getContext(), sbn,
+ new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "misc"));
+ mService.addNotification(r);
+ return r;
}
//
@@ -185,6 +253,11 @@
eq(false), (AudioAttributes) anyObject());
}
+ private void verifyCustomBeep() throws RemoteException {
+ verify(mRingtonePlayer, times(1)).playAsync(eq(CUSTOM_SOUND), (UserHandle) anyObject(),
+ eq(false), (AudioAttributes) anyObject());
+ }
+
private void verifyNeverStopAudio() throws RemoteException {
verify(mRingtonePlayer, never()).stopAsync();
}
@@ -208,6 +281,11 @@
eq(0), (AudioAttributes) anyObject());
}
+ private void verifyCustomVibrate() {
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(CUSTOM_VIBRATION), eq(-1),
+ (AudioAttributes) anyObject());
+ }
+
private void verifyStopVibrate() {
verify(mVibrator, times(1)).cancel();
}
@@ -216,10 +294,33 @@
verify(mVibrator, never()).cancel();
}
+ private void verifyLights() {
+ verify(mStatusBar, times(1)).notificationLightPulse(anyInt(), anyInt(), anyInt());
+ }
+
+ private void verifyCustomLights() {
+ verify(mStatusBar, times(1)).notificationLightPulse(
+ eq(CUSTOM_LIGHT_COLOR), eq(CUSTOM_LIGHT_ON), eq(CUSTOM_LIGHT_OFF));
+ }
+
private Context getContext() {
return InstrumentationRegistry.getTargetContext();
}
+ //
+ // Tests
+ //
+
+ @Test
+ public void testLights() throws Exception {
+ NotificationRecord r = getLightsNotification();
+ r.setImportance(NotificationManager.IMPORTANCE_DEFAULT, "for testing");
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyLights();
+ }
+
@Test
public void testBeep() throws Exception {
NotificationRecord r = getBeepyNotification();
@@ -230,9 +331,40 @@
verifyNeverVibrate();
}
- //
- // Tests
- //
+ @Test
+ public void testBeepFromChannel() throws Exception {
+ NotificationRecord r = getQuietNotification();
+ r.getChannel().setDefaultRingtone(Settings.System.DEFAULT_NOTIFICATION_URI);
+ r.setImportance(NotificationManager.IMPORTANCE_DEFAULT, "for testing");
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyBeepLooped();
+ verifyNeverVibrate();
+ }
+
+ @Test
+ public void testVibrateFromChannel() throws Exception {
+ NotificationRecord r = getQuietNotification();
+ r.getChannel().setVibration(true);
+ r.setImportance(NotificationManager.IMPORTANCE_DEFAULT, "for testing");
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyNeverBeep();
+ verifyVibrate();
+ }
+
+ @Test
+ public void testLightsFromChannel() throws Exception {
+ NotificationRecord r = getQuietNotification();
+ r.setImportance(NotificationManager.IMPORTANCE_DEFAULT, "for testing");
+ r.getChannel().setLights(true);
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyLights();
+ }
@Test
public void testBeepInsistently() throws Exception {
@@ -244,6 +376,36 @@
}
@Test
+ public void testChannelNoOverwriteCustomVibration() throws Exception {
+ NotificationRecord r = getCustomBuzzyOnceNotification();
+ r.getChannel().setVibration(true);
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyCustomVibrate();
+ }
+
+ @Test
+ public void testChannelNoOverwriteCustomBeep() throws Exception {
+ NotificationRecord r = getCustomBeepyNotification();
+ r.getChannel().setDefaultRingtone(Settings.System.DEFAULT_RINGTONE_URI);
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyCustomBeep();
+ }
+
+ @Test
+ public void testChannelNoOverwriteCustomLights() throws Exception {
+ NotificationRecord r = getCustomLightsNotification();
+ r.getChannel().setLights(true);
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyCustomLights();
+ }
+
+ @Test
public void testNoInterruptionForMin() throws Exception {
NotificationRecord r = getBeepyNotification();
r.setImportance(Ranking.IMPORTANCE_MIN, "foo");
@@ -393,7 +555,7 @@
}
@Test
- public void testDemotInsistenteSoundToVibrate() throws Exception {
+ public void testDemoteInsistenteSoundToVibrate() throws Exception {
NotificationRecord r = getInsistentBeepyNotification();
// the phone is quiet
diff --git a/services/tests/servicestests/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/servicestests/src/com/android/server/notification/ImportanceExtractorTest.java
new file mode 100644
index 0000000..3cbde1d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/notification/ImportanceExtractorTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.app.NotificationManager;
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import static org.junit.Assert.assertEquals;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ImportanceExtractorTest {
+
+ @Mock RankingConfig mConfig;
+
+ private String mPkg = "com.android.server.notification";
+ private int mId = 1001;
+ private int mOtherId = 1002;
+ private String mTag = null;
+ private int mUid = 1000;
+ private int mPid = 2000;
+ private int mScore = 10;
+ private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private NotificationRecord getNotificationRecord(NotificationChannel channel) {
+ final Builder builder = new Builder(getContext())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setDefaults(Notification.DEFAULT_SOUND);
+
+ Notification n = builder.build();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
+ mPid, mScore, n, mUser, System.currentTimeMillis());
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+ return r;
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ //
+ // Tests
+ //
+
+ @Test
+ public void testAppPreferenceChannelNone() throws Exception {
+ ImportanceExtractor extractor = new ImportanceExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.getImportance(anyString(), anyInt())).thenReturn(
+ NotificationManager.IMPORTANCE_MIN);
+ NotificationChannel channel = new NotificationChannel("a", "a");
+ channel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_MIN);
+ }
+
+ @Test
+ public void testAppPreferenceChannelPermissive() throws Exception {
+ ImportanceExtractor extractor = new ImportanceExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.getImportance(anyString(), anyInt())).thenReturn(
+ NotificationManager.IMPORTANCE_MIN);
+ NotificationChannel channel = new NotificationChannel("a", "a");
+ channel.setImportance(NotificationManager.IMPORTANCE_HIGH);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_MIN);
+ }
+
+ @Test
+ public void testAppPreferenceChannelStrict() throws Exception {
+ ImportanceExtractor extractor = new ImportanceExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.getImportance(anyString(), anyInt())).thenReturn(
+ NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel = new NotificationChannel("a", "a");
+ channel.setImportance(NotificationManager.IMPORTANCE_MIN);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_MIN);
+ }
+
+ @Test
+ public void testNoAppPreferenceChannelPreference() throws Exception {
+ ImportanceExtractor extractor = new ImportanceExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.getImportance(anyString(), anyInt())).thenReturn(
+ NotificationManager.IMPORTANCE_UNSPECIFIED);
+ NotificationChannel channel = new NotificationChannel("a", "a");
+ channel.setImportance(NotificationManager.IMPORTANCE_MIN);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_MIN);
+ }
+
+ @Test
+ public void testNoPreferences() throws Exception {
+ ImportanceExtractor extractor = new ImportanceExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.getImportance(anyString(), anyInt())).thenReturn(
+ NotificationManager.IMPORTANCE_UNSPECIFIED);
+ NotificationChannel channel = new NotificationChannel("a", "a");
+ channel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertEquals(r.getUserImportance(),
+ NotificationManager.IMPORTANCE_UNSPECIFIED);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/servicestests/src/com/android/server/notification/RankingHelperTest.java
index e890a48..ee33dcc 100644
--- a/services/tests/servicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -18,19 +18,33 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import com.android.internal.util.FastXmlSerializer;
+
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
import android.app.Notification;
import android.content.Context;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.net.Uri;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Xml;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@SmallTest
@@ -70,7 +84,8 @@
.setWhen(1205)
.build();
mRecordGroupGSortA = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", 1, null, 0, 0, 0, mNotiGroupGSortA, user));
+ "package", "package", 1, null, 0, 0, 0, mNotiGroupGSortA, user),
+ getDefaultChannel());
mNotiGroupGSortB = new Notification.Builder(getContext())
.setContentTitle("B")
@@ -79,21 +94,24 @@
.setWhen(1200)
.build();
mRecordGroupGSortB = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", 1, null, 0, 0, 0, mNotiGroupGSortB, user));
+ "package", "package", 1, null, 0, 0, 0, mNotiGroupGSortB, user),
+ getDefaultChannel());
mNotiNoGroup = new Notification.Builder(getContext())
.setContentTitle("C")
.setWhen(1201)
.build();
mRecordNoGroup = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", 1, null, 0, 0, 0, mNotiNoGroup, user));
+ "package", "package", 1, null, 0, 0, 0, mNotiNoGroup, user),
+ getDefaultChannel());
mNotiNoGroup2 = new Notification.Builder(getContext())
.setContentTitle("D")
.setWhen(1202)
.build();
mRecordNoGroup2 = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", 1, null, 0, 0, 0, mNotiNoGroup2, user));
+ "package", "package", 1, null, 0, 0, 0, mNotiNoGroup2, user),
+ getDefaultChannel());
mNotiNoGroupSortA = new Notification.Builder(getContext())
.setContentTitle("E")
@@ -101,9 +119,14 @@
.setSortKey("A")
.build();
mRecordNoGroupSortA = new NotificationRecord(getContext(), new StatusBarNotification(
- "package", "package", 1, null, 0, 0, 0, mNotiNoGroupSortA, user));
+ "package", "package", 1, null, 0, 0, 0, mNotiNoGroupSortA, user),
+ getDefaultChannel());
}
+ private NotificationChannel getDefaultChannel() {
+ return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name");
+ }
+
@Test
public void testFindAfterRankingWithASplitGroup() throws Exception {
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
@@ -153,4 +176,45 @@
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>();
mHelper.sort(notificationList);
}
+
+ @Test
+ public void testChannelXml() throws Exception {
+ String pkg = "com.android.server.notification";
+ int uid = 0;
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1");
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2");
+ channel2.setImportance(NotificationManager.IMPORTANCE_LOW);
+ channel2.setDefaultRingtone(new Uri.Builder().scheme("test").build());
+ channel2.setLights(true);
+ channel2.setBypassDnd(true);
+ channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+
+ mHelper.createNotificationChannel(pkg, uid, channel1);
+ mHelper.createNotificationChannel(pkg, uid, channel2);
+
+ byte[] data;
+ XmlSerializer serializer = new FastXmlSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, "ranking");
+ mHelper.writeXml(serializer, false);
+ serializer.endTag(null, "ranking");
+ serializer.endDocument();
+ serializer.flush();
+
+ mHelper.deleteNotificationChannel(pkg, uid, channel1.getId());
+ mHelper.deleteNotificationChannel(pkg, uid, channel2.getId());
+ mHelper.deleteNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
+
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ mHelper.readXml(parser, false);
+
+ assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId()));
+ assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId()));
+ assertNotNull(
+ mHelper.getNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 09bd12c..53c6a33 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -25,10 +25,13 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
@@ -64,6 +67,8 @@
import java.io.PrintStream;
import java.security.PublicKey;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@@ -180,6 +185,10 @@
private static final String PACKAGE_NAME = "com.android.bar";
private static final String REAL_PACKAGE_NAME = "com.android.foo";
+ private static final String PARENT_PACKAGE_NAME = "com.android.bar.parent";
+ private static final String CHILD_PACKAGE_NAME_01 = "com.android.bar.child01";
+ private static final String CHILD_PACKAGE_NAME_02 = "com.android.bar.child02";
+ private static final String CHILD_PACKAGE_NAME_03 = "com.android.bar.child03";
private static final File INITIAL_CODE_PATH =
new File(InstrumentationRegistry.getContext().getFilesDir(), "com.android.bar-1");
private static final File UPDATED_CODE_PATH =
@@ -187,20 +196,177 @@
private static final int INITIAL_VERSION_CODE = 10023;
private static final int UPDATED_VERSION_CODE = 10025;
- /** Update existing package; don't install */
@Test
- public void testUpdatePackageSetting01()
- throws ReflectiveOperationException, PackageManagerException {
- final PackageSetting pkgSetting01 = createPackageSetting(0 /*pkgFlags*/);
- final PackageSetting oldPkgSetting01 = new PackageSetting(pkgSetting01);
- final PackageSetting testPkgSetting01 = Settings.updatePackageSetting(
- pkgSetting01,
+ public void testPackageStateCopy01() {
+ final List<String> childPackageNames = new ArrayList<>();
+ childPackageNames.add(CHILD_PACKAGE_NAME_01);
+ childPackageNames.add(CHILD_PACKAGE_NAME_02);
+ childPackageNames.add(CHILD_PACKAGE_NAME_03);
+ final PackageSetting origPkgSetting01 = new PackageSetting(
PACKAGE_NAME,
- null /*realPkgName*/,
- null /*originalPkg*/,
+ REAL_PACKAGE_NAME,
+ INITIAL_CODE_PATH /*codePath*/,
+ INITIAL_CODE_PATH /*resourcePath*/,
+ null /*legacyNativeLibraryPathString*/,
+ "x86_64" /*primaryCpuAbiString*/,
+ "x86" /*secondaryCpuAbiString*/,
+ null /*cpuAbiOverrideString*/,
+ INITIAL_VERSION_CODE,
+ ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
+ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
+ PARENT_PACKAGE_NAME,
+ childPackageNames,
+ 0);
+ final PackageSetting testPkgSetting01 = new PackageSetting(origPkgSetting01);
+ verifySettingCopy(origPkgSetting01, testPkgSetting01);
+ }
+
+ @Test
+ public void testPackageStateCopy02() {
+ final List<String> childPackageNames = new ArrayList<>();
+ childPackageNames.add(CHILD_PACKAGE_NAME_01);
+ childPackageNames.add(CHILD_PACKAGE_NAME_02);
+ childPackageNames.add(CHILD_PACKAGE_NAME_03);
+ final PackageSetting origPkgSetting01 = new PackageSetting(
+ PACKAGE_NAME /*pkgName*/,
+ REAL_PACKAGE_NAME /*realPkgName*/,
+ INITIAL_CODE_PATH /*codePath*/,
+ INITIAL_CODE_PATH /*resourcePath*/,
+ null /*legacyNativeLibraryPathString*/,
+ "x86_64" /*primaryCpuAbiString*/,
+ "x86" /*secondaryCpuAbiString*/,
+ null /*cpuAbiOverrideString*/,
+ INITIAL_VERSION_CODE,
+ ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
+ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
+ PARENT_PACKAGE_NAME,
+ childPackageNames,
+ 0);
+ final PackageSetting testPkgSetting01 = new PackageSetting(
+ PACKAGE_NAME /*pkgName*/,
+ REAL_PACKAGE_NAME /*realPkgName*/,
+ UPDATED_CODE_PATH /*codePath*/,
+ UPDATED_CODE_PATH /*resourcePath*/,
+ null /*legacyNativeLibraryPathString*/,
+ null /*primaryCpuAbiString*/,
+ null /*secondaryCpuAbiString*/,
+ null /*cpuAbiOverrideString*/,
+ UPDATED_VERSION_CODE,
+ 0 /*pkgFlags*/,
+ 0 /*pkgPrivateFlags*/,
+ null /*parentPkgName*/,
+ null /*childPkgNames*/,
+ 0);
+ testPkgSetting01.copyFrom(origPkgSetting01);
+ verifySettingCopy(origPkgSetting01, testPkgSetting01);
+ }
+
+ /** Update package */
+ @Test
+ public void testUpdatePackageSetting01() throws PackageManagerException {
+ final PackageSetting testPkgSetting01 =
+ createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+ testPkgSetting01.setInstalled(false /*installed*/, 0 /*userId*/);
+ assertThat(testPkgSetting01.pkgFlags, is(0));
+ assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
+ final PackageSetting oldPkgSetting01 = new PackageSetting(testPkgSetting01);
+ Settings.updatePackageSetting(
+ testPkgSetting01,
null /*disabledPkg*/,
null /*sharedUser*/,
UPDATED_CODE_PATH /*codePath*/,
+ null /*legacyNativeLibraryPath*/,
+ "arm64-v8a" /*primaryCpuAbi*/,
+ "armeabi" /*secondaryCpuAbi*/,
+ 0 /*pkgFlags*/,
+ 0 /*pkgPrivateFlags*/,
+ null /*childPkgNames*/,
+ UserManagerService.getInstance());
+ assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
+ assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
+ assertThat(testPkgSetting01.origPackage, is(nullValue()));
+ assertThat(testPkgSetting01.pkgFlags, is(0));
+ assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
+ final PackageUserState userState = testPkgSetting01.readUserState(0);
+ final PackageUserState oldUserState = oldPkgSetting01.readUserState(0);
+ verifyUserState(userState, oldUserState, false /*userStateChanged*/, false /*notLaunched*/,
+ false /*stopped*/, false /*installed*/);
+ }
+
+ /** Update package; package now on /system, install for user '0' */
+ @Test
+ public void testUpdatePackageSetting02() throws PackageManagerException {
+ final PackageSetting testPkgSetting01 =
+ createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+ testPkgSetting01.setInstalled(false /*installed*/, 0 /*userId*/);
+ assertThat(testPkgSetting01.pkgFlags, is(0));
+ assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
+ final PackageSetting oldPkgSetting01 = new PackageSetting(testPkgSetting01);
+ Settings.updatePackageSetting(
+ testPkgSetting01,
+ null /*disabledPkg*/,
+ null /*sharedUser*/,
+ UPDATED_CODE_PATH /*codePath*/,
+ null /*legacyNativeLibraryPath*/,
+ "arm64-v8a" /*primaryCpuAbi*/,
+ "armeabi" /*secondaryCpuAbi*/,
+ ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
+ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
+ null /*childPkgNames*/,
+ UserManagerService.getInstance());
+ assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
+ assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
+ assertThat(testPkgSetting01.origPackage, is(nullValue()));
+ assertThat(testPkgSetting01.pkgFlags, is(ApplicationInfo.FLAG_SYSTEM));
+ assertThat(testPkgSetting01.pkgPrivateFlags, is(ApplicationInfo.PRIVATE_FLAG_PRIVILEGED));
+ final PackageUserState userState = testPkgSetting01.readUserState(0);
+ final PackageUserState oldUserState = oldPkgSetting01.readUserState(0);
+ // WARNING: When creating a shallow copy of the PackageSetting we do NOT create
+ // new contained objects. For example, this means that changes to the user state
+ // in testPkgSetting01 will also change the user state in its copy.
+ verifyUserState(userState, oldUserState, false /*userStateChanged*/, false /*notLaunched*/,
+ false /*stopped*/, true /*installed*/);
+ }
+
+ /** Update package; changing shared user throws exception */
+ @Test
+ public void testUpdatePackageSetting03() {
+ final Settings testSettings01 = new Settings(new Object() /*lock*/);
+ final SharedUserSetting testUserSetting01 = createSharedUserSetting(
+ testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+ final PackageSetting testPkgSetting01 =
+ createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+ try {
+ Settings.updatePackageSetting(
+ testPkgSetting01,
+ null /*disabledPkg*/,
+ testUserSetting01 /*sharedUser*/,
+ UPDATED_CODE_PATH /*codePath*/,
+ null /*legacyNativeLibraryPath*/,
+ "arm64-v8a" /*primaryCpuAbi*/,
+ "armeabi" /*secondaryCpuAbi*/,
+ 0 /*pkgFlags*/,
+ 0 /*pkgPrivateFlags*/,
+ null /*childPkgNames*/,
+ UserManagerService.getInstance());
+ fail("Expected a PackageManagerException");
+ } catch (PackageManagerException expected) {
+ }
+ }
+
+ /** Create a new PackageSetting based on an original package setting */
+ @Test
+ public void testCreateNewSetting01() {
+ final PackageSetting originalPkgSetting01 =
+ createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+ final PackageSignatures originalSignatures = originalPkgSetting01.signatures;
+ final PackageSetting testPkgSetting01 = Settings.createNewSetting(
+ REAL_PACKAGE_NAME,
+ originalPkgSetting01 /*originalPkg*/,
+ null /*disabledPkg*/,
+ null /*realPkgName*/,
+ null /*sharedUser*/,
+ UPDATED_CODE_PATH /*codePath*/,
UPDATED_CODE_PATH /*resourcePath*/,
null /*legacyNativeLibraryPath*/,
"arm64-v8a" /*primaryCpuAbi*/,
@@ -213,36 +379,37 @@
null /*parentPkgName*/,
null /*childPkgNames*/,
UserManagerService.getInstance());
- assertThat(testPkgSetting01, is(pkgSetting01));
- assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
- assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
- assertThat(testPkgSetting01.origPackage, is(nullValue()));
+ assertThat(testPkgSetting01.codePath, is(UPDATED_CODE_PATH));
+ assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
assertThat(testPkgSetting01.pkgFlags, is(ApplicationInfo.FLAG_SYSTEM));
assertThat(testPkgSetting01.pkgPrivateFlags, is(ApplicationInfo.PRIVATE_FLAG_PRIVILEGED));
- final PackageUserState userState = testPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- final PackageUserState oldUserState = oldPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- verifyUserState(userState, oldUserState, false /*userStateChanged*/);
+ assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
+ assertThat(testPkgSetting01.resourcePath, is(UPDATED_CODE_PATH));
+ assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
+ assertSame(testPkgSetting01.origPackage, originalPkgSetting01);
+ // signatures object must be different
+ assertNotSame(testPkgSetting01.signatures, originalSignatures);
+ assertThat(testPkgSetting01.versionCode, is(UPDATED_VERSION_CODE));
+ final PackageUserState userState = testPkgSetting01.readUserState(0);
+ verifyUserState(userState, null /*oldUserState*/, false /*userStateChanged*/,
+ false /*notLaunched*/, false /*stopped*/, true /*installed*/);
}
- /** Update existing package; install for UserHandle.SYSTEM */
+ /** Create a new non-system PackageSetting */
@Test
- public void testUpdatePackageSetting02()
- throws ReflectiveOperationException, PackageManagerException {
- final PackageSetting pkgSetting01 = createPackageSetting(0 /*pkgFlags*/);
- final PackageSetting oldPkgSetting01 = new PackageSetting(pkgSetting01);
- final PackageSetting testPkgSetting01 = Settings.updatePackageSetting(
- pkgSetting01,
+ public void testCreateNewSetting02() {
+ final PackageSetting testPkgSetting01 = Settings.createNewSetting(
PACKAGE_NAME,
- null /*realPkgName*/,
null /*originalPkg*/,
null /*disabledPkg*/,
+ null /*realPkgName*/,
null /*sharedUser*/,
- UPDATED_CODE_PATH /*codePath*/,
- UPDATED_CODE_PATH /*resourcePath*/,
+ INITIAL_CODE_PATH /*codePath*/,
+ INITIAL_CODE_PATH /*resourcePath*/,
null /*legacyNativeLibraryPath*/,
- "arm64-v8a" /*primaryCpuAbi*/,
- "armeabi" /*secondaryCpuAbi*/,
- UPDATED_VERSION_CODE /*versionCode*/,
+ "x86_64" /*primaryCpuAbiString*/,
+ "x86" /*secondaryCpuAbiString*/,
+ INITIAL_VERSION_CODE /*versionCode*/,
0 /*pkgFlags*/,
0 /*pkgPrivateFlags*/,
UserHandle.SYSTEM /*installUser*/,
@@ -250,104 +417,74 @@
null /*parentPkgName*/,
null /*childPkgNames*/,
UserManagerService.getInstance());
- assertThat(testPkgSetting01, is(pkgSetting01));
- assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
- assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
+ assertThat(testPkgSetting01.appId, is(0));
+ assertThat(testPkgSetting01.codePath, is(INITIAL_CODE_PATH));
+ assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
assertThat(testPkgSetting01.origPackage, is(nullValue()));
assertThat(testPkgSetting01.pkgFlags, is(0));
assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
- final PackageUserState userState = testPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- final PackageUserState oldUserState = oldPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- // The user state won't be changed in this scenario; the default user state is for
- // the package to be installed.
- verifyUserState(userState, oldUserState, false /*userStateChanged*/,
- false /*notLaunched*/, false /*stopped*/);
- }
-
- /** Update existing package; install for {@code null} */
- @Test
- public void testUpdatePackageSetting03()
- throws ReflectiveOperationException, PackageManagerException {
- final PackageSetting pkgSetting01 = createPackageSetting(0 /*pkgFlags*/);
- final PackageSetting oldPkgSetting01 = new PackageSetting(pkgSetting01);
- final PackageSetting testPkgSetting01 = Settings.updatePackageSetting(
- pkgSetting01,
- PACKAGE_NAME,
- null /*realPkgName*/,
- null /*originalPkg*/,
- null /*disabledPkg*/,
- null /*sharedUser*/,
- UPDATED_CODE_PATH /*codePath*/,
- UPDATED_CODE_PATH /*resourcePath*/,
- null /*legacyNativeLibraryPath*/,
- "arm64-v8a" /*primaryCpuAbi*/,
- "armeabi" /*secondaryCpuAbi*/,
- UPDATED_VERSION_CODE /*versionCode*/,
- 0 /*pkgFlags*/,
- 0 /*pkgPrivateFlags*/,
- null /*installUser*/,
- true /*allowInstall*/,
- null /*parentPkgName*/,
- null /*childPkgNames*/,
- UserManagerService.getInstance());
- assertThat(testPkgSetting01, is(pkgSetting01));
- assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
- assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
- assertThat(testPkgSetting01.origPackage, is(nullValue()));
- assertThat(testPkgSetting01.pkgFlags, is(0));
- assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
- final PackageUserState userState = testPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- final PackageUserState oldUserState = oldPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- verifyUserState(userState, oldUserState, false /*userStateChanged*/);
- }
-
- /** Update renamed package */
- @Test
- public void testUpdatePackageSetting04()
- throws ReflectiveOperationException, PackageManagerException {
- final PackageSetting originalPkgSetting = createPackageSetting(0 /*pkgFlags*/);
- final PackageSetting testPkgSetting01 = Settings.updatePackageSetting(
- null /*pkgSetting*/,
- PACKAGE_NAME,
- null /*realPkgName*/,
- originalPkgSetting /*originalPkg*/,
- null /*disabledPkg*/,
- null /*sharedUser*/,
- UPDATED_CODE_PATH /*codePath*/,
- UPDATED_CODE_PATH /*resourcePath*/,
- null /*legacyNativeLibraryPath*/,
- "arm64-v8a" /*primaryCpuAbi*/,
- "armeabi" /*secondaryCpuAbi*/,
- UPDATED_VERSION_CODE /*versionCode*/,
- 0 /*pkgFlags*/,
- 0 /*pkgPrivateFlags*/,
- UserHandle.SYSTEM /*installUser*/,
- false /*allowInstall*/,
- null /*parentPkgName*/,
- null /*childPkgNames*/,
- UserManagerService.getInstance());
- assertThat(testPkgSetting01, is(not(originalPkgSetting)));
- // ABI isn't pulled from the original package setting
assertThat(testPkgSetting01.primaryCpuAbiString, is("x86_64"));
+ assertThat(testPkgSetting01.resourcePath, is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.secondaryCpuAbiString, is("x86"));
- assertThat(testPkgSetting01.origPackage, is(originalPkgSetting));
- assertThat(testPkgSetting01.pkgFlags, is(0));
- assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
- final PackageUserState userState = testPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- verifyUserState(userState, PackageSettingBase.DEFAULT_USER_STATE /*oldUserState*/,
- false /*userStateChanged*/);
+ assertThat(testPkgSetting01.versionCode, is(INITIAL_VERSION_CODE));
+ // by default, the package is considered stopped
+ final PackageUserState userState = testPkgSetting01.readUserState(0);
+ verifyUserState(userState, null /*oldUserState*/, false /*userStateChanged*/,
+ true /*notLaunched*/, true /*stopped*/, true /*installed*/);
}
- /** Update new package */
+ /** Create PackageSetting for a shared user */
@Test
- public void testUpdatePackageSetting05()
- throws ReflectiveOperationException, PackageManagerException {
- final PackageSetting testPkgSetting01 = Settings.updatePackageSetting(
- null /*pkgSetting*/,
+ public void testCreateNewSetting03() {
+ final Settings testSettings01 = new Settings(new Object() /*lock*/);
+ final SharedUserSetting testUserSetting01 = createSharedUserSetting(
+ testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+ final PackageSetting testPkgSetting01 = Settings.createNewSetting(
PACKAGE_NAME,
- null /*realPkgName*/,
null /*originalPkg*/,
null /*disabledPkg*/,
+ null /*realPkgName*/,
+ testUserSetting01 /*sharedUser*/,
+ INITIAL_CODE_PATH /*codePath*/,
+ INITIAL_CODE_PATH /*resourcePath*/,
+ null /*legacyNativeLibraryPath*/,
+ "x86_64" /*primaryCpuAbiString*/,
+ "x86" /*secondaryCpuAbiString*/,
+ INITIAL_VERSION_CODE /*versionCode*/,
+ 0 /*pkgFlags*/,
+ 0 /*pkgPrivateFlags*/,
+ null /*installUser*/,
+ false /*allowInstall*/,
+ null /*parentPkgName*/,
+ null /*childPkgNames*/,
+ UserManagerService.getInstance());
+ assertThat(testPkgSetting01.appId, is(10064));
+ assertThat(testPkgSetting01.codePath, is(INITIAL_CODE_PATH));
+ assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
+ assertThat(testPkgSetting01.origPackage, is(nullValue()));
+ assertThat(testPkgSetting01.pkgFlags, is(0));
+ assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
+ assertThat(testPkgSetting01.primaryCpuAbiString, is("x86_64"));
+ assertThat(testPkgSetting01.resourcePath, is(INITIAL_CODE_PATH));
+ assertThat(testPkgSetting01.secondaryCpuAbiString, is("x86"));
+ assertThat(testPkgSetting01.versionCode, is(INITIAL_VERSION_CODE));
+ final PackageUserState userState = testPkgSetting01.readUserState(0);
+ verifyUserState(userState, null /*oldUserState*/, false /*userStateChanged*/,
+ false /*notLaunched*/, false /*stopped*/, true /*installed*/);
+ }
+
+ /** Create a new PackageSetting based on a disabled package setting */
+ @Test
+ public void testCreateNewSetting04() {
+ final PackageSetting disabledPkgSetting01 =
+ createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+ disabledPkgSetting01.appId = 10064;
+ final PackageSignatures disabledSignatures = disabledPkgSetting01.signatures;
+ final PackageSetting testPkgSetting01 = Settings.createNewSetting(
+ PACKAGE_NAME,
+ null /*originalPkg*/,
+ disabledPkgSetting01 /*disabledPkg*/,
+ null /*realPkgName*/,
null /*sharedUser*/,
UPDATED_CODE_PATH /*codePath*/,
UPDATED_CODE_PATH /*resourcePath*/,
@@ -362,144 +499,34 @@
null /*parentPkgName*/,
null /*childPkgNames*/,
UserManagerService.getInstance());
- assertThat(testPkgSetting01, is(notNullValue()));
- assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
- assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
+ assertThat(testPkgSetting01.appId, is(10064));
+ assertThat(testPkgSetting01.codePath, is(UPDATED_CODE_PATH));
+ assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
assertThat(testPkgSetting01.origPackage, is(nullValue()));
assertThat(testPkgSetting01.pkgFlags, is(0));
assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
- final PackageUserState userState = testPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- verifyUserState(userState, PackageSettingBase.DEFAULT_USER_STATE /*oldUserState*/,
- false /*userStateChanged*/);
- }
-
- /** Update new package; install for {@code null} user */
- @Test
- public void testUpdatePackageSetting06()
- throws ReflectiveOperationException, PackageManagerException {
- final PackageSetting testPkgSetting01 = Settings.updatePackageSetting(
- null /*pkgSetting*/,
- PACKAGE_NAME,
- null /*realPkgName*/,
- null /*originalPkg*/,
- null /*disabledPkg*/,
- null /*sharedUser*/,
- UPDATED_CODE_PATH /*codePath*/,
- UPDATED_CODE_PATH /*resourcePath*/,
- null /*legacyNativeLibraryPath*/,
- "arm64-v8a" /*primaryCpuAbi*/,
- "armeabi" /*secondaryCpuAbi*/,
- UPDATED_VERSION_CODE /*versionCode*/,
- 0 /*pkgFlags*/,
- 0 /*pkgPrivateFlags*/,
- null /*installUser*/,
- true /*allowInstall*/,
- null /*parentPkgName*/,
- null /*childPkgNames*/,
- UserManagerService.getInstance());
- assertThat(testPkgSetting01, is(notNullValue()));
assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
+ assertThat(testPkgSetting01.resourcePath, is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
- assertThat(testPkgSetting01.origPackage, is(nullValue()));
- assertThat(testPkgSetting01.pkgFlags, is(0));
- assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
- final PackageUserState userState = testPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- verifyUserState(userState, PackageSettingBase.DEFAULT_USER_STATE /*oldUserState*/,
- true /*userStateChanged*/, true /*notLaunched*/, true /*stopped*/);
- }
-
- /** Update new package; install for UserHandle.SYSTEM */
- @Test
- public void testUpdatePackageSetting07()
- throws ReflectiveOperationException, PackageManagerException {
- final PackageSetting testPkgSetting01 = Settings.updatePackageSetting(
- null /*pkgSetting*/,
- PACKAGE_NAME,
- null /*realPkgName*/,
- null /*originalPkg*/,
- null /*disabledPkg*/,
- null /*sharedUser*/,
- UPDATED_CODE_PATH /*codePath*/,
- UPDATED_CODE_PATH /*resourcePath*/,
- null /*legacyNativeLibraryPath*/,
- "arm64-v8a" /*primaryCpuAbi*/,
- "armeabi" /*secondaryCpuAbi*/,
- UPDATED_VERSION_CODE /*versionCode*/,
- 0 /*pkgFlags*/,
- 0 /*pkgPrivateFlags*/,
- UserHandle.SYSTEM /*installUser*/,
- true /*allowInstall*/,
- null /*parentPkgName*/,
- null /*childPkgNames*/,
- UserManagerService.getInstance());
- assertThat(testPkgSetting01, is(notNullValue()));
- assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
- assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
- assertThat(testPkgSetting01.origPackage, is(nullValue()));
- assertThat(testPkgSetting01.pkgFlags, is(0));
- assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
- final PackageUserState userState = testPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- verifyUserState(userState, PackageSettingBase.DEFAULT_USER_STATE /*oldUserState*/,
- true /*userStateChanged*/, true /*notLaunched*/, true /*stopped*/);
- }
-
- /** Update package, but, shared user changed; ensure old setting not modified */
- @Test
- public void testUpdatePackageSetting08()
- throws ReflectiveOperationException, PackageManagerException {
- final SharedUserSetting testSharedUser =
- new SharedUserSetting("testSharedUser", 0 /*pkgFlags*/, 0 /*_pkgPrivateFlags*/);
- testSharedUser.userId = Process.FIRST_APPLICATION_UID + 9995;
- final PackageSetting pkgSetting01 = createPackageSetting(0 /*pkgFlags*/);
- final PackageSetting oldPkgSetting01 = new PackageSetting(pkgSetting01);
- final PackageSetting testPkgSetting01 = Settings.updatePackageSetting(
- pkgSetting01,
- PACKAGE_NAME,
- null /*realPkgName*/,
- null /*originalPkg*/,
- null /*disabledPkg*/,
- testSharedUser /*sharedUser*/,
- UPDATED_CODE_PATH /*codePath*/,
- UPDATED_CODE_PATH /*resourcePath*/,
- null /*legacyNativeLibraryPath*/,
- "arm64-v8a" /*primaryCpuAbi*/,
- "armeabi" /*secondaryCpuAbi*/,
- UPDATED_VERSION_CODE /*versionCode*/,
- 0 /*pkgFlags*/,
- 0 /*pkgPrivateFlags*/,
- UserHandle.SYSTEM /*installUser*/,
- true /*allowInstall*/,
- null /*parentPkgName*/,
- null /*childPkgNames*/,
- UserManagerService.getInstance());
- assertThat(testPkgSetting01, is(not(pkgSetting01)));
- assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
- assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
- assertThat(testPkgSetting01.origPackage, is(nullValue()));
- assertThat(testPkgSetting01.appId, is(19995));
- assertThat(testPkgSetting01.pkgFlags, is(0));
- assertThat(testPkgSetting01.pkgPrivateFlags, is(0));
- // package setting should not have been modified
- assertThat(pkgSetting01.primaryCpuAbiString, is("x86_64"));
- assertThat(pkgSetting01.secondaryCpuAbiString, is("x86"));
- final PackageUserState userState = testPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- final PackageUserState oldUserState = oldPkgSetting01.readUserState(UserHandle.USER_SYSTEM);
- verifyUserState(userState, oldUserState, true /*userStateChanged*/,
- true /*notLaunched*/, true /*stopped*/);
+ assertNotSame(testPkgSetting01.signatures, disabledSignatures);
+ assertThat(testPkgSetting01.versionCode, is(UPDATED_VERSION_CODE));
+ final PackageUserState userState = testPkgSetting01.readUserState(0);
+ verifyUserState(userState, null /*oldUserState*/, false /*userStateChanged*/,
+ false /*notLaunched*/, false /*stopped*/, true /*installed*/);
}
private void verifyUserState(PackageUserState userState, PackageUserState oldUserState,
boolean userStateChanged) {
verifyUserState(userState, oldUserState, userStateChanged, false /*notLaunched*/,
- false /*stopped*/);
+ false /*stopped*/, true /*installed*/);
}
private void verifyUserState(PackageUserState userState, PackageUserState oldUserState,
- boolean userStateChanged, boolean notLaunched, boolean stopped) {
+ boolean userStateChanged, boolean notLaunched, boolean stopped, boolean installed) {
assertThat(userState.blockUninstall, is(false));
assertThat(userState.enabled, is(0));
assertThat(userState.hidden, is(false));
- assertThat(userState.installed, is(true));
+ assertThat(userState.installed, is(installed));
assertThat(userState.notLaunched, is(notLaunched));
assertThat(userState.stopped, is(stopped));
assertThat(userState.suspended, is(false));
@@ -508,7 +535,81 @@
}
}
- private PackageSetting createPackageSetting(int pkgFlags) {
+ private void verifySettingCopy(PackageSetting origPkgSetting, PackageSetting testPkgSetting) {
+ assertThat(origPkgSetting, is(not(testPkgSetting)));
+ assertThat(origPkgSetting.appId, is(testPkgSetting.appId));
+ // different but equal objects
+ assertNotSame(origPkgSetting.childPackageNames, testPkgSetting.childPackageNames);
+ assertThat(origPkgSetting.childPackageNames, is(testPkgSetting.childPackageNames));
+ assertSame(origPkgSetting.codePath, testPkgSetting.codePath);
+ assertThat(origPkgSetting.codePath, is(testPkgSetting.codePath));
+ assertSame(origPkgSetting.codePathString, testPkgSetting.codePathString);
+ assertThat(origPkgSetting.codePathString, is(testPkgSetting.codePathString));
+ assertSame(origPkgSetting.cpuAbiOverrideString, testPkgSetting.cpuAbiOverrideString);
+ assertThat(origPkgSetting.cpuAbiOverrideString, is(testPkgSetting.cpuAbiOverrideString));
+ assertThat(origPkgSetting.firstInstallTime, is(testPkgSetting.firstInstallTime));
+ assertSame(origPkgSetting.installerPackageName, testPkgSetting.installerPackageName);
+ assertThat(origPkgSetting.installerPackageName, is(testPkgSetting.installerPackageName));
+ assertThat(origPkgSetting.installPermissionsFixed,
+ is(testPkgSetting.installPermissionsFixed));
+ assertThat(origPkgSetting.installStatus, is(testPkgSetting.installStatus));
+ assertThat(origPkgSetting.isOrphaned, is(testPkgSetting.isOrphaned));
+ assertSame(origPkgSetting.keySetData, testPkgSetting.keySetData);
+ assertThat(origPkgSetting.keySetData, is(testPkgSetting.keySetData));
+ assertThat(origPkgSetting.lastUpdateTime, is(testPkgSetting.lastUpdateTime));
+ assertSame(origPkgSetting.legacyNativeLibraryPathString,
+ testPkgSetting.legacyNativeLibraryPathString);
+ assertThat(origPkgSetting.legacyNativeLibraryPathString,
+ is(testPkgSetting.legacyNativeLibraryPathString));
+ assertNotSame(origPkgSetting.mPermissionsState, testPkgSetting.mPermissionsState);
+ assertThat(origPkgSetting.mPermissionsState, is(testPkgSetting.mPermissionsState));
+ assertThat(origPkgSetting.name, is(testPkgSetting.name));
+ // oldCodePaths is _not_ copied
+ // assertNotSame(origPkgSetting.oldCodePaths, testPkgSetting.oldCodePaths);
+ // assertThat(origPkgSetting.oldCodePaths, is(not(testPkgSetting.oldCodePaths)));
+ assertSame(origPkgSetting.origPackage, testPkgSetting.origPackage);
+ assertThat(origPkgSetting.origPackage, is(testPkgSetting.origPackage));
+ assertSame(origPkgSetting.parentPackageName, testPkgSetting.parentPackageName);
+ assertThat(origPkgSetting.parentPackageName, is(testPkgSetting.parentPackageName));
+ assertSame(origPkgSetting.pkg, testPkgSetting.pkg);
+ // No equals() method for this object
+ // assertThat(origPkgSetting.pkg, is(testPkgSetting.pkg));
+ assertThat(origPkgSetting.pkgFlags, is(testPkgSetting.pkgFlags));
+ assertThat(origPkgSetting.pkgPrivateFlags, is(testPkgSetting.pkgPrivateFlags));
+ assertSame(origPkgSetting.primaryCpuAbiString, testPkgSetting.primaryCpuAbiString);
+ assertThat(origPkgSetting.primaryCpuAbiString, is(testPkgSetting.primaryCpuAbiString));
+ assertThat(origPkgSetting.realName, is(testPkgSetting.realName));
+ assertSame(origPkgSetting.resourcePath, testPkgSetting.resourcePath);
+ assertThat(origPkgSetting.resourcePath, is(testPkgSetting.resourcePath));
+ assertSame(origPkgSetting.resourcePathString, testPkgSetting.resourcePathString);
+ assertThat(origPkgSetting.resourcePathString, is(testPkgSetting.resourcePathString));
+ assertSame(origPkgSetting.secondaryCpuAbiString, testPkgSetting.secondaryCpuAbiString);
+ assertThat(origPkgSetting.secondaryCpuAbiString, is(testPkgSetting.secondaryCpuAbiString));
+ assertSame(origPkgSetting.sharedUser, testPkgSetting.sharedUser);
+ assertThat(origPkgSetting.sharedUser, is(testPkgSetting.sharedUser));
+ assertSame(origPkgSetting.signatures, testPkgSetting.signatures);
+ assertThat(origPkgSetting.signatures, is(testPkgSetting.signatures));
+ assertThat(origPkgSetting.timeStamp, is(testPkgSetting.timeStamp));
+ assertThat(origPkgSetting.uidError, is(testPkgSetting.uidError));
+ assertNotSame(origPkgSetting.getUserState(), is(testPkgSetting.getUserState()));
+ // No equals() method for SparseArray object
+ // assertThat(origPkgSetting.getUserState(), is(testPkgSetting.getUserState()));
+ assertSame(origPkgSetting.verificationInfo, testPkgSetting.verificationInfo);
+ assertThat(origPkgSetting.verificationInfo, is(testPkgSetting.verificationInfo));
+ assertThat(origPkgSetting.versionCode, is(testPkgSetting.versionCode));
+ assertSame(origPkgSetting.volumeUuid, testPkgSetting.volumeUuid);
+ assertThat(origPkgSetting.volumeUuid, is(testPkgSetting.volumeUuid));
+ }
+
+ private SharedUserSetting createSharedUserSetting(Settings settings, String userName,
+ int sharedUserId, int pkgFlags, int pkgPrivateFlags) {
+ return settings.addSharedUserLPw(
+ userName,
+ sharedUserId,
+ pkgFlags,
+ pkgPrivateFlags);
+ }
+ private PackageSetting createPackageSetting(int sharedUserId, int pkgFlags) {
return new PackageSetting(
PACKAGE_NAME,
REAL_PACKAGE_NAME,
@@ -523,7 +624,7 @@
0 /*privateFlags*/,
null /*parentPackageName*/,
null /*childPackageNames*/,
- 0 /*sharedUserId*/);
+ sharedUserId);
}
private @NonNull List<UserInfo> createFakeUsers() {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 143398f..3cfdc32 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -3813,9 +3813,9 @@
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1");
addPackage(CALLING_PACKAGE_2, CALLING_UID_1, 10, "sig1", "sig2");
- final ShortcutPackageInfo spi1 = ShortcutPackageInfo.generateForInstalledPackage(
+ final ShortcutPackageInfo spi1 = ShortcutPackageInfo.generateForInstalledPackageForTest(
mService, CALLING_PACKAGE_1, USER_0);
- final ShortcutPackageInfo spi2 = ShortcutPackageInfo.generateForInstalledPackage(
+ final ShortcutPackageInfo spi2 = ShortcutPackageInfo.generateForInstalledPackageForTest(
mService, CALLING_PACKAGE_2, USER_0);
checkCanRestoreTo(true, spi1, 10, "sig1");
@@ -5661,6 +5661,32 @@
buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_P0);
});
});
+ // Check the user-IDs.
+ assertEquals(USER_0,
+ mService.getUserShortcutsLocked(USER_0).getPackageShortcuts(CALLING_PACKAGE_1)
+ .getOwnerUserId());
+ assertEquals(USER_0,
+ mService.getUserShortcutsLocked(USER_0).getPackageShortcuts(CALLING_PACKAGE_1)
+ .getPackageUserId());
+ assertEquals(USER_P0,
+ mService.getUserShortcutsLocked(USER_P0).getPackageShortcuts(CALLING_PACKAGE_1)
+ .getOwnerUserId());
+ assertEquals(USER_P0,
+ mService.getUserShortcutsLocked(USER_P0).getPackageShortcuts(CALLING_PACKAGE_1)
+ .getPackageUserId());
+
+ assertEquals(USER_0,
+ mService.getUserShortcutsLocked(USER_0).getLauncherShortcuts(LAUNCHER_1, USER_0)
+ .getOwnerUserId());
+ assertEquals(USER_0,
+ mService.getUserShortcutsLocked(USER_0).getLauncherShortcuts(LAUNCHER_1, USER_0)
+ .getPackageUserId());
+ assertEquals(USER_P0,
+ mService.getUserShortcutsLocked(USER_P0).getLauncherShortcuts(LAUNCHER_1, USER_0)
+ .getOwnerUserId());
+ assertEquals(USER_0,
+ mService.getUserShortcutsLocked(USER_P0).getLauncherShortcuts(LAUNCHER_1, USER_0)
+ .getPackageUserId());
}
public void testOnApplicationActive_permission() {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
new file mode 100644
index 0000000..35967fb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.UserManagerInternal;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.MediumTest;
+
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.LinkedHashSet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * <p>Run with:<pre>
+ * m FrameworksServicesTests &&
+ * adb install \
+ * -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ * adb shell am instrument -e class com.android.server.pm.UserManagerServiceIdRecyclingTest \
+ * -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserManagerServiceIdRecyclingTest {
+ private UserManagerService mUserManagerService;
+
+ @Before
+ public void setup() {
+ // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup
+ // TODO: Remove once UMS supports proper dependency injection
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
+ }
+
+ @Test
+ public void testUserCreateRecycleIdsAddAllThenRemove() {
+ // Add max possible users
+ for (int i = UserManagerService.MIN_USER_ID; i < UserManagerService.MAX_USER_ID; i++) {
+ int userId = mUserManagerService.getNextAvailableId();
+ assertEquals(i, userId);
+ mUserManagerService.putUserInfo(newUserInfo(userId));
+ }
+
+ assertNoNextIdAvailable("All ids should be assigned");
+ // Now remove RECENTLY_REMOVED_IDS_MAX_SIZE users in the middle
+ int startFrom = UserManagerService.MIN_USER_ID + 10000 /* arbitrary number */;
+ int lastId = startFrom + UserManagerService.MAX_RECENTLY_REMOVED_IDS_SIZE;
+ for (int i = startFrom; i < lastId; i++) {
+ removeUser(i);
+ assertNoNextIdAvailable("There is not enough recently removed users. "
+ + "Next id should not be available. Failed at u" + i);
+ }
+
+ // Now remove first user
+ removeUser(UserManagerService.MIN_USER_ID);
+
+ // Released UserIDs should be returned in the FIFO order
+ int nextId = mUserManagerService.getNextAvailableId();
+ assertEquals(startFrom, nextId);
+ }
+
+ @Test
+ public void testUserCreateRecycleIdsOverflow() {
+ LinkedHashSet<Integer> queue = new LinkedHashSet<>();
+ // Make sure we can generate more than 2x ids without issues
+ for (int i = 0; i < UserManagerService.MAX_USER_ID * 2; i++) {
+ int userId = mUserManagerService.getNextAvailableId();
+ assertTrue("Returned id should not be recent. Id=" + userId + ". Recents=" + queue,
+ queue.add(userId));
+ if (queue.size() > UserManagerService.MAX_RECENTLY_REMOVED_IDS_SIZE) {
+ queue.remove(queue.iterator().next());
+ }
+ mUserManagerService.putUserInfo(newUserInfo(userId));
+ removeUser(userId);
+ }
+ }
+
+ private void removeUser(int userId) {
+ mUserManagerService.removeUserInfo(userId);
+ mUserManagerService.addRemovingUserIdLocked(userId);
+ }
+
+ private void assertNoNextIdAvailable(String message) {
+ try {
+ mUserManagerService.getNextAvailableId();
+ fail(message);
+ } catch (IllegalStateException e) {
+ //OK
+ }
+ }
+
+ private static UserInfo newUserInfo(int userId) {
+ return new UserInfo(userId, "User " + userId, 0);
+ }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 32fd43a..27d9d10 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -21,12 +21,12 @@
import org.junit.runner.RunWith;
import android.content.Context;
+import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.IWindow;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -42,21 +42,17 @@
* Run: adb shell am instrument -w -e class com.android.server.wm.AppWindowTokenTests com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
*/
@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class AppWindowTokenTests {
private static WindowManagerService sWm = null;
- private final WindowManagerPolicy mPolicy = new TestWindowManagerPolicy();
private final IWindow mIWindow = new TestIWindow();
@Before
public void setUp() throws Exception {
final Context context = InstrumentationRegistry.getTargetContext();
- if (sWm == null) {
- // We only want to do this once for the test process as we don't want WM to try to
- // register a bunch of local services again.
- sWm = WindowManagerService.main(context, null, true, false, false, mPolicy);
- }
+ sWm = TestWindowManagerPolicy.getWindowManagerService(context);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 8c6b007..0afbd0c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -82,6 +82,18 @@
public class TestWindowManagerPolicy implements WindowManagerPolicy {
private static final String TAG = "TestWindowManagerPolicy";
+ private static WindowManagerService sWm = null;
+
+ static synchronized WindowManagerService getWindowManagerService(Context context) {
+ if (sWm == null) {
+ // We only want to do this once for the test process as we don't want WM to try to
+ // register a bunch of local services again.
+ sWm = WindowManagerService.main(
+ context, null, true, false, false, new TestWindowManagerPolicy());
+ }
+ return sWm;
+ }
+
@Override
public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index a15b74b..973f1b9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -19,12 +19,17 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import java.util.Comparator;
-import java.util.LinkedList;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -39,6 +44,7 @@
* Run: adb shell am instrument -w -e class com.android.server.wm.WindowContainerTests com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
*/
@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class WindowContainerTests {
@@ -82,6 +88,31 @@
}
@Test
+ public void testAdd_AlreadyHasParent() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child2 = root.addChildWindow();
+
+ boolean gotException = false;
+ try {
+ child1.addChildWindow(child2);
+ } catch (IllegalArgumentException e) {
+ gotException = true;
+ }
+ assertTrue(gotException);
+
+ gotException = false;
+ try {
+ root.addChildWindow(child2);
+ } catch (IllegalArgumentException e) {
+ gotException = true;
+ }
+ assertTrue(gotException);
+ }
+
+ @Test
public void testHasChild() throws Exception {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
final TestWindowContainer root = builder.setLayer(0).build();
@@ -195,7 +226,7 @@
final TestWindowContainer child12 = child1.addChildWindow(builder.setIsVisible(true));
final TestWindowContainer child21 = child2.addChildWindow();
- assertTrue(root.isVisible());
+ assertFalse(root.isVisible());
assertTrue(child1.isVisible());
assertFalse(child11.isVisible());
assertTrue(child12.isVisible());
@@ -204,7 +235,7 @@
}
@Test
- public void testDetachChild() throws Exception {
+ public void testRemoveChild() throws Exception {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow();
@@ -214,7 +245,7 @@
assertTrue(root.hasChild(child2));
assertTrue(root.hasChild(child21));
- root.detachChild(child2);
+ root.removeChild(child2);
assertFalse(root.hasChild(child2));
assertFalse(root.hasChild(child21));
assertNull(child2.getParentWindow());
@@ -223,46 +254,177 @@
assertTrue(root.hasChild(child11));
try {
// Can only detach our direct children.
- root.detachChild(child11);
+ root.removeChild(child11);
} catch (IllegalArgumentException e) {
gotException = true;
}
assertTrue(gotException);
}
+ @Test
+ public void testGetOrientation_Unset() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
+ // Unspecified well because we didn't specify anything...
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, root.getOrientation());
+ }
+
+ @Test
+ public void testGetOrientation_InvisibleParentUnsetVisibleChildren() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
+
+ builder.setIsVisible(false).setLayer(-1);
+ final TestWindowContainer invisible = root.addChildWindow(builder);
+ builder.setIsVisible(true).setLayer(-2);
+ final TestWindowContainer invisibleChild1VisibleAndSet = invisible.addChildWindow(builder);
+ invisibleChild1VisibleAndSet.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ // Landscape well because the container is visible and that is what we set on it above.
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, invisibleChild1VisibleAndSet.getOrientation());
+ // Unset because the container isn't visible even though it has a child that thinks it is
+ // visible.
+ assertEquals(SCREEN_ORIENTATION_UNSET, invisible.getOrientation());
+ // Unspecified because we are visible and we didn't specify an orientation and there isn't
+ // a visible child.
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, root.getOrientation());
+
+ builder.setIsVisible(true).setLayer(-3);
+ final TestWindowContainer visibleUnset = root.addChildWindow(builder);
+ visibleUnset.setOrientation(SCREEN_ORIENTATION_UNSET);
+ assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnset.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, root.getOrientation());
+
+ }
+
+ @Test
+ public void testGetOrientation_setBehind() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
+
+ builder.setIsVisible(true).setLayer(-1);
+ final TestWindowContainer visibleUnset = root.addChildWindow(builder);
+ visibleUnset.setOrientation(SCREEN_ORIENTATION_UNSET);
+
+ builder.setIsVisible(true).setLayer(-2);
+ final TestWindowContainer visibleUnsetChild1VisibleSetBehind =
+ visibleUnset.addChildWindow(builder);
+ visibleUnsetChild1VisibleSetBehind.setOrientation(SCREEN_ORIENTATION_BEHIND);
+ // Setting to visible behind will be used by the parents if there isn't another other
+ // container behind this one that has an orientation set.
+ assertEquals(SCREEN_ORIENTATION_BEHIND,
+ visibleUnsetChild1VisibleSetBehind.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_BEHIND, visibleUnset.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation());
+ }
+
+ @Test
+ public void testGetOrientation_fillsParent() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
+
+ builder.setIsVisible(true).setLayer(-1);
+ final TestWindowContainer visibleUnset = root.addChildWindow(builder);
+ visibleUnset.setOrientation(SCREEN_ORIENTATION_BEHIND);
+
+ builder.setLayer(1).setIsVisible(true);
+ final TestWindowContainer visibleUnspecifiedRootChild = root.addChildWindow(builder);
+ visibleUnspecifiedRootChild.setFillsParent(false);
+ visibleUnspecifiedRootChild.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
+ // Unset because the child doesn't fill the parent. May as well be invisible...
+ assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnspecifiedRootChild.getOrientation());
+ // The parent uses whatever orientation is set behind this container since it doesn't fill
+ // the parent.
+ assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation());
+
+ // Test case of child filling its parent, but its parent isn't filling its own parent.
+ builder.setLayer(2).setIsVisible(true);
+ final TestWindowContainer visibleUnspecifiedRootChildChildFillsParent =
+ visibleUnspecifiedRootChild.addChildWindow(builder);
+ visibleUnspecifiedRootChildChildFillsParent.setOrientation(
+ SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT,
+ visibleUnspecifiedRootChildChildFillsParent.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnspecifiedRootChild.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation());
+
+
+ visibleUnspecifiedRootChild.setFillsParent(true);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, visibleUnspecifiedRootChild.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, root.getOrientation());
+ }
+
+ @Test
+ public void testCompareTo() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child1 = root.addChildWindow();
+ final TestWindowContainer child11 = child1.addChildWindow();
+ final TestWindowContainer child12 = child1.addChildWindow();
+
+ final TestWindowContainer child2 = root.addChildWindow();
+ final TestWindowContainer child21 = child2.addChildWindow();
+ final TestWindowContainer child22 = child2.addChildWindow();
+ final TestWindowContainer child23 = child2.addChildWindow();
+ final TestWindowContainer child221 = child22.addChildWindow();
+ final TestWindowContainer child222 = child22.addChildWindow();
+ final TestWindowContainer child223 = child22.addChildWindow();
+ final TestWindowContainer child2221 = child222.addChildWindow();
+ final TestWindowContainer child2222 = child222.addChildWindow();
+ final TestWindowContainer child2223 = child222.addChildWindow();
+
+ final TestWindowContainer root2 = builder.setLayer(0).build();
+
+ assertEquals(0, root.compareTo(root));
+ assertEquals(-1, child1.compareTo(child2));
+ assertEquals(1, child2.compareTo(child1));
+
+ boolean inTheSameTree = true;
+ try {
+ root.compareTo(root2);
+ } catch (IllegalArgumentException e) {
+ inTheSameTree = false;
+ }
+ assertFalse(inTheSameTree);
+
+ assertEquals(-1, child1.compareTo(child11));
+ assertEquals(1, child21.compareTo(root));
+ assertEquals(1, child21.compareTo(child12));
+ assertEquals(-1, child11.compareTo(child2));
+ assertEquals(1, child2221.compareTo(child11));
+ assertEquals(-1, child2222.compareTo(child223));
+ assertEquals(1, child2223.compareTo(child21));
+ }
+
/* Used so we can gain access to some protected members of the {@link WindowContainer} class */
- private class TestWindowContainer extends WindowContainer {
+ private class TestWindowContainer extends WindowContainer<TestWindowContainer> {
private final int mLayer;
- private final LinkedList<String> mUsers = new LinkedList();
private final boolean mCanDetach;
private boolean mIsAnimating;
private boolean mIsVisible;
- private int mRemoveIfPossibleCount;
- private int mRemoveImmediatelyCount;
+ private boolean mFillsParent;
/**
* Compares 2 window layers and returns -1 if the first is lesser than the second in terms
* of z-order and 1 otherwise.
*/
- private final Comparator<WindowContainer> mWindowSubLayerComparator = (w1, w2) -> {
- final int layer1 = ((TestWindowContainer)w1).mLayer;
- final int layer2 = ((TestWindowContainer)w2).mLayer;
+ private final Comparator<TestWindowContainer> mWindowSubLayerComparator = (w1, w2) -> {
+ final int layer1 = w1.mLayer;
+ final int layer2 = w2.mLayer;
if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
- // We insert the child window into the list ordered by the mLayer.
- // For same layers, the negative one should go below others; the positive one should
- // go above others.
+ // We insert the child window into the list ordered by the mLayer. For same layers,
+ // the negative one should go below others; the positive one should go above others.
return -1;
}
return 1;
};
- TestWindowContainer(int layer, LinkedList<String> users, boolean canDetach,
- boolean isAnimating, boolean isVisible) {
+ TestWindowContainer(int layer, boolean canDetach, boolean isAnimating, boolean isVisible) {
mLayer = layer;
- mUsers.addAll(users);
mCanDetach = canDetach;
mIsAnimating = isAnimating;
mIsVisible = isVisible;
+ mFillsParent = true;
}
TestWindowContainer getParentWindow() {
@@ -273,6 +435,11 @@
return mChildren.size();
}
+ TestWindowContainer addChildWindow(TestWindowContainer child) {
+ addChild(child, mWindowSubLayerComparator);
+ return child;
+ }
+
TestWindowContainer addChildWindow(TestWindowContainerBuilder childBuilder) {
TestWindowContainer child = childBuilder.build();
addChild(child, mWindowSubLayerComparator);
@@ -284,7 +451,7 @@
}
TestWindowContainer getChildAt(int index) {
- return (TestWindowContainer) mChildren.get(index);
+ return mChildren.get(index);
}
@Override
@@ -299,36 +466,31 @@
@Override
boolean isVisible() {
- return mIsVisible || super.isVisible();
+ return mIsVisible;
}
@Override
- void removeImmediately() {
- super.removeImmediately();
- mRemoveImmediatelyCount++;
+ boolean fillsParent() {
+ return mFillsParent;
}
- @Override
- void removeIfPossible() {
- super.removeIfPossible();
- mRemoveIfPossibleCount++;
+ void setFillsParent(boolean fillsParent) {
+ mFillsParent = fillsParent;
}
}
private class TestWindowContainerBuilder {
private int mLayer;
- private LinkedList<String> mUsers = new LinkedList();
private boolean mCanDetach;
private boolean mIsAnimating;
private boolean mIsVisible;
- TestWindowContainerBuilder setLayer(int layer) {
- mLayer = layer;
- return this;
+ public TestWindowContainerBuilder() {
+ reset();
}
- TestWindowContainerBuilder addUser(String user) {
- mUsers.add(user);
+ TestWindowContainerBuilder setLayer(int layer) {
+ mLayer = layer;
return this;
}
@@ -349,7 +511,6 @@
TestWindowContainerBuilder reset() {
mLayer = 0;
- mUsers.clear();
mCanDetach = false;
mIsAnimating = false;
mIsVisible = false;
@@ -357,7 +518,7 @@
}
TestWindowContainer build() {
- return new TestWindowContainer(mLayer, mUsers, mCanDetach, mIsAnimating, mIsVisible);
+ return new TestWindowContainer(mLayer, mCanDetach, mIsAnimating, mIsVisible);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index 4b29a60..1259e0f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -21,12 +21,13 @@
import org.junit.runner.RunWith;
import android.content.Context;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.IWindow;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -45,23 +46,19 @@
* Run: adb shell am instrument -w -e class com.android.server.wm.WindowStateTests com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
*/
@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class WindowStateTests {
private static WindowManagerService sWm = null;
private WindowToken mWindowToken;
- private final WindowManagerPolicy mPolicy = new TestWindowManagerPolicy();
private final IWindow mIWindow = new TestIWindow();
@Before
public void setUp() throws Exception {
final Context context = InstrumentationRegistry.getTargetContext();
- if (sWm == null) {
- // We only want to do this once for the test process as we don't want WM to try to
- // register a bunch of local services again.
- sWm = WindowManagerService.main(context, null, true, false, false, mPolicy);
- }
- mWindowToken = new WindowToken(sWm, null, 0, false);
+ sWm = TestWindowManagerPolicy.getWindowManagerService(context);
+ mWindowToken = new WindowToken(sWm, new Binder(), 0, false);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
index 4505254..3279886 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
@@ -21,12 +21,12 @@
import org.junit.runner.RunWith;
import android.content.Context;
+import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.IWindow;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -43,21 +43,17 @@
* Run: adb shell am instrument -w -e class com.android.server.wm.WindowTokenTests com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
*/
@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class WindowTokenTests {
- private static WindowManagerService sWm = null;
- private final WindowManagerPolicy mPolicy = new TestWindowManagerPolicy();
+ private WindowManagerService mWm = null;
private final IWindow mIWindow = new TestIWindow();
@Before
public void setUp() throws Exception {
final Context context = InstrumentationRegistry.getTargetContext();
- if (sWm == null) {
- // We only want to do this once for the test process as we don't want WM to try to
- // register a bunch of local services again.
- sWm = WindowManagerService.main(context, null, true, false, false, mPolicy);
- }
+ mWm = TestWindowManagerPolicy.getWindowManagerService(context);
}
@Test
@@ -161,15 +157,15 @@
private WindowState createWindow(WindowState parent, int type, WindowToken token) {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
- return new WindowState(sWm, null, mIWindow, token, parent, 0, 0, attrs, 0,
- sWm.getDefaultDisplayContentLocked(), 0);
+ return new WindowState(mWm, null, mIWindow, token, parent, 0, 0, attrs, 0,
+ mWm.getDefaultDisplayContentLocked(), 0);
}
/* Used so we can gain access to some protected members of the {@link WindowToken} class */
private class TestWindowToken extends WindowToken {
TestWindowToken() {
- super(sWm, null, 0, false);
+ super(mWm, null, 0, false);
}
int getWindowsCount() {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 8284773..04104b5 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -94,7 +94,7 @@
static final String TAG = "UsageStatsService";
- static final boolean DEBUG = false;
+ static final boolean DEBUG = false; // Never submit with true
static final boolean COMPRESS_TIME = false;
private static final long TEN_SECONDS = 10 * 1000;
@@ -139,8 +139,8 @@
long mSystemTimeSnapshot;
boolean mAppIdleEnabled;
- boolean mAppIdleParoled;
- private boolean mScreenOn;
+ boolean mAppIdleTempParoled;
+ boolean mCharging;
private long mLastAppIdleParoledTime;
private volatile boolean mPendingOneTimeCheckIdleStates;
@@ -191,7 +191,7 @@
mAppIdleEnabled = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableAutoPowerModes);
if (mAppIdleEnabled) {
- IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING);
+ IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
getContext().registerReceiver(new DeviceStateReceiver(), deviceStates);
@@ -237,7 +237,7 @@
mSystemServicesReady = true;
} else if (phase == PHASE_BOOT_COMPLETED) {
- setAppIdleParoled(getContext().getSystemService(BatteryManager.class).isCharging());
+ setChargingState(getContext().getSystemService(BatteryManager.class).isCharging());
}
}
@@ -284,9 +284,8 @@
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (BatteryManager.ACTION_CHARGING.equals(action)
- || BatteryManager.ACTION_DISCHARGING.equals(action)) {
- setAppIdleParoled(BatteryManager.ACTION_CHARGING.equals(action));
+ if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+ setChargingState(intent.getIntExtra("plugged", 0) != 0);
} else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
onDeviceIdleModeChanged();
}
@@ -376,12 +375,21 @@
}
}
+ void setChargingState(boolean charging) {
+ synchronized (mLock) {
+ if (mCharging != charging) {
+ mCharging = charging;
+ postParoleStateChanged();
+ }
+ }
+ }
+
/** Paroled here means temporary pardon from being inactive */
void setAppIdleParoled(boolean paroled) {
synchronized (mLock) {
- if (mAppIdleParoled != paroled) {
- mAppIdleParoled = paroled;
- if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleParoled);
+ if (mAppIdleTempParoled != paroled) {
+ mAppIdleTempParoled = paroled;
+ if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
if (paroled) {
postParoleEndTimeout();
} else {
@@ -393,6 +401,12 @@
}
}
+ boolean isParoledOrCharging() {
+ synchronized (mLock) {
+ return mAppIdleTempParoled || mCharging;
+ }
+ }
+
private void postNextParoleTimeout() {
if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
@@ -495,7 +509,7 @@
/** Check if it's been a while since last parole and let idle apps do some work */
void checkParoleTimeout() {
synchronized (mLock) {
- if (!mAppIdleParoled) {
+ if (!mAppIdleTempParoled) {
final long timeSinceLastParole = checkAndGetTimeLocked() - mLastAppIdleParoledTime;
if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
@@ -786,7 +800,7 @@
}
boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime) {
- if (mAppIdleParoled) {
+ if (isParoledOrCharging()) {
return false;
}
return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
@@ -989,8 +1003,9 @@
}
void informParoleStateChanged() {
+ final boolean paroled = isParoledOrCharging();
for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
- listener.onParoleStateChanged(mAppIdleParoled);
+ listener.onParoleStateChanged(paroled);
}
}
@@ -1072,9 +1087,9 @@
pw.println();
pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
- pw.print(" mAppIdleParoled="); pw.print(mAppIdleParoled);
- pw.print(" mScreenOn="); pw.println(mScreenOn);
- pw.print("mLastAppIdleParoledTime=");
+ pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled);
+ pw.print(" mCharging="); pw.print(mCharging);
+ pw.print(" mLastAppIdleParoledTime=");
TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
pw.println();
}
@@ -1139,7 +1154,8 @@
break;
case MSG_PAROLE_STATE_CHANGED:
- if (DEBUG) Slog.d(TAG, "Parole state changed: " + mAppIdleParoled);
+ if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
+ + ", Charging state:" + mCharging);
informParoleStateChanged();
break;
@@ -1466,7 +1482,7 @@
@Override
public boolean isAppIdleParoleOn() {
- return mAppIdleParoled;
+ return isParoledOrCharging();
}
@Override
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index efd479f..220626a 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -137,7 +137,7 @@
private final Context mContext;
private final ContentResolver mContentResolver;
@GuardedBy("mLock")
- private UsbSettingsManager mCurrentSettings;
+ private UsbProfileGroupSettingsManager mCurrentSettings;
private NotificationManager mNotificationManager;
private final boolean mHasUsbAccessory;
private boolean mUseUsbNotification;
@@ -150,6 +150,7 @@
private String[] mAccessoryStrings;
private UsbDebuggingManager mDebuggingManager;
private final UsbAlsaManager mUsbAlsaManager;
+ private final UsbSettingsManager mSettingsManager;
private Intent mBroadcastedIntent;
private class AdbSettingsObserver extends ContentObserver {
@@ -192,9 +193,11 @@
}
};
- public UsbDeviceManager(Context context, UsbAlsaManager alsaManager) {
+ public UsbDeviceManager(Context context, UsbAlsaManager alsaManager,
+ UsbSettingsManager settingsManager) {
mContext = context;
mUsbAlsaManager = alsaManager;
+ mSettingsManager = settingsManager;
mContentResolver = context.getContentResolver();
PackageManager pm = mContext.getPackageManager();
mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
@@ -218,7 +221,7 @@
new IntentFilter(UsbManager.ACTION_USB_PORT_CHANGED));
}
- private UsbSettingsManager getCurrentSettings() {
+ private UsbProfileGroupSettingsManager getCurrentSettings() {
synchronized (mLock) {
return mCurrentSettings;
}
@@ -255,10 +258,10 @@
mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
}
- public void setCurrentUser(int userId, UsbSettingsManager settings) {
+ public void setCurrentUser(int newCurrentUserId, UsbProfileGroupSettingsManager settings) {
synchronized (mLock) {
mCurrentSettings = settings;
- mHandler.obtainMessage(MSG_USER_SWITCHED, userId, 0).sendToTarget();
+ mHandler.obtainMessage(MSG_USER_SWITCHED, newCurrentUserId, 0).sendToTarget();
}
}
@@ -584,7 +587,7 @@
if (mCurrentAccessory != null) {
if (mBootCompleted) {
- getCurrentSettings().accessoryDetached(mCurrentAccessory);
+ mSettingsManager.usbAccessoryRemoved(mCurrentAccessory);
}
mCurrentAccessory = null;
mAccessoryStrings = null;
@@ -947,7 +950,7 @@
}
/* opens the currently attached USB accessory */
- public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
+ public ParcelFileDescriptor openAccessory(UsbAccessory accessory, UsbUserSettingsManager settings) {
UsbAccessory currentAccessory = mHandler.getCurrentAccessory();
if (currentAccessory == null) {
throw new IllegalArgumentException("no accessory attached");
@@ -958,7 +961,7 @@
+ currentAccessory;
throw new IllegalArgumentException(error);
}
- getCurrentSettings().checkPermission(accessory);
+ settings.checkPermission(accessory);
return nativeOpenAccessory();
}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 1d850e1..b789e17 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -32,8 +32,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
@@ -62,18 +60,21 @@
private ArrayList<UsbEndpoint> mNewEndpoints;
private final UsbAlsaManager mUsbAlsaManager;
+ private final UsbSettingsManager mSettingsManager;
@GuardedBy("mLock")
- private UsbSettingsManager mCurrentSettings;
+ private UsbProfileGroupSettingsManager mCurrentSettings;
@GuardedBy("mLock")
private ComponentName mUsbDeviceConnectionHandler;
- public UsbHostManager(Context context, UsbAlsaManager alsaManager) {
+ public UsbHostManager(Context context, UsbAlsaManager alsaManager,
+ UsbSettingsManager settingsManager) {
mContext = context;
mHostBlacklist = context.getResources().getStringArray(
com.android.internal.R.array.config_usbHostBlacklist);
mUsbAlsaManager = alsaManager;
+ mSettingsManager = settingsManager;
String deviceConnectionHandler = context.getResources().getString(
com.android.internal.R.string.config_UsbDeviceConnectionHandling_component);
if (!TextUtils.isEmpty(deviceConnectionHandler)) {
@@ -82,13 +83,13 @@
}
}
- public void setCurrentSettings(UsbSettingsManager settings) {
+ public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) {
synchronized (mLock) {
mCurrentSettings = settings;
}
}
- private UsbSettingsManager getCurrentSettings() {
+ private UsbProfileGroupSettingsManager getCurrentUserSettings() {
synchronized (mLock) {
return mCurrentSettings;
}
@@ -247,11 +248,14 @@
new UsbConfiguration[mNewConfigurations.size()]));
mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
Slog.d(TAG, "Added device " + mNewDevice);
+
+ // It is fine to call this only for the current user as all broadcasts are sent to
+ // all profiles of the user and the dialogs should only show once.
ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
if (usbDeviceConnectionHandler == null) {
- getCurrentSettings().deviceAttached(mNewDevice);
+ getCurrentUserSettings().deviceAttached(mNewDevice);
} else {
- getCurrentSettings().deviceAttachedForFixedHandler(mNewDevice,
+ getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice,
usbDeviceConnectionHandler);
}
mUsbAlsaManager.usbDeviceAdded(mNewDevice);
@@ -273,7 +277,8 @@
UsbDevice device = mDevices.remove(deviceName);
if (device != null) {
mUsbAlsaManager.usbDeviceRemoved(device);
- getCurrentSettings().deviceDetached(device);
+ mSettingsManager.usbDeviceRemoved(device);
+ getCurrentUserSettings().usbDeviceRemoved(device);
}
}
}
@@ -301,7 +306,7 @@
}
/* Opens the specified USB device */
- public ParcelFileDescriptor openDevice(String deviceName) {
+ public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) {
synchronized (mLock) {
if (isBlackListed(deviceName)) {
throw new SecurityException("USB device is on a restricted bus");
@@ -312,7 +317,7 @@
throw new IllegalArgumentException(
"device " + deviceName + " does not exist or is restricted");
}
- getCurrentSettings().checkPermission(device);
+ settings.checkPermission(device);
return nativeOpenDevice(deviceName);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
new file mode 100644
index 0000000..cc0fb8d
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -0,0 +1,1300 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.content.res.XmlResourceParser;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import libcore.io.IoUtils;
+
+import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
+
+class UsbProfileGroupSettingsManager {
+ private static final String TAG = UsbProfileGroupSettingsManager.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /** Legacy settings file, before multi-user */
+ private static final File sSingleUserSettingsFile = new File(
+ "/data/system/usb_device_manager.xml");
+
+ /** The parent user (main user of the profile group) */
+ private final UserHandle mParentUser;
+
+ private final AtomicFile mSettingsFile;
+ private final boolean mDisablePermissionDialogs;
+
+ private final Context mContext;
+
+ private final PackageManager mPackageManager;
+
+ private final UserManager mUserManager;
+ private final @NonNull UsbSettingsManager mSettingsManager;
+
+ // Maps DeviceFilter to user preferred application package
+ private final HashMap<DeviceFilter, UserPackage> mDevicePreferenceMap = new HashMap<>();
+ // Maps AccessoryFilter to user preferred application package
+ private final HashMap<AccessoryFilter, UserPackage> mAccessoryPreferenceMap = new HashMap<>();
+
+ private final Object mLock = new Object();
+
+ /**
+ * A package of a user.
+ */
+ @Immutable
+ private static class UserPackage {
+ /** User */
+ final @NonNull UserHandle user;
+
+ /** Package name */
+ final @NonNull String packageName;
+
+ /**
+ * Create a description of a per user package.
+ *
+ * @param packageName The name of the package
+ * @param user The user
+ */
+ private UserPackage(@NonNull String packageName, @NonNull UserHandle user) {
+ this.packageName = packageName;
+ this.user = user;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof UserPackage)) {
+ return false;
+ } else {
+ UserPackage other = (UserPackage)obj;
+
+ return user.equals(user) && packageName.equals(other.packageName);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int result = user.hashCode();
+ result = 31 * result + packageName.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return user.getIdentifier() + "/" + packageName;
+ }
+ }
+
+ // This class is used to describe a USB device.
+ // When used in HashMaps all values must be specified,
+ // but wildcards can be used for any of the fields in
+ // the package meta-data.
+ private static class DeviceFilter {
+ // USB Vendor ID (or -1 for unspecified)
+ public final int mVendorId;
+ // USB Product ID (or -1 for unspecified)
+ public final int mProductId;
+ // USB device or interface class (or -1 for unspecified)
+ public final int mClass;
+ // USB device subclass (or -1 for unspecified)
+ public final int mSubclass;
+ // USB device protocol (or -1 for unspecified)
+ public final int mProtocol;
+ // USB device manufacturer name string (or null for unspecified)
+ public final String mManufacturerName;
+ // USB device product name string (or null for unspecified)
+ public final String mProductName;
+ // USB device serial number string (or null for unspecified)
+ public final String mSerialNumber;
+
+ public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
+ String manufacturer, String product, String serialnum) {
+ mVendorId = vid;
+ mProductId = pid;
+ mClass = clasz;
+ mSubclass = subclass;
+ mProtocol = protocol;
+ mManufacturerName = manufacturer;
+ mProductName = product;
+ mSerialNumber = serialnum;
+ }
+
+ public DeviceFilter(UsbDevice device) {
+ mVendorId = device.getVendorId();
+ mProductId = device.getProductId();
+ mClass = device.getDeviceClass();
+ mSubclass = device.getDeviceSubclass();
+ mProtocol = device.getDeviceProtocol();
+ mManufacturerName = device.getManufacturerName();
+ mProductName = device.getProductName();
+ mSerialNumber = device.getSerialNumber();
+ }
+
+ public static DeviceFilter read(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int vendorId = -1;
+ int productId = -1;
+ int deviceClass = -1;
+ int deviceSubclass = -1;
+ int deviceProtocol = -1;
+ String manufacturerName = null;
+ String productName = null;
+ String serialNumber = null;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ // Attribute values are ints or strings
+ if ("manufacturer-name".equals(name)) {
+ manufacturerName = value;
+ } else if ("product-name".equals(name)) {
+ productName = value;
+ } else if ("serial-number".equals(name)) {
+ serialNumber = value;
+ } else {
+ int intValue = -1;
+ int radix = 10;
+ if (value != null && value.length() > 2 && value.charAt(0) == '0' &&
+ (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
+ // allow hex values starting with 0x or 0X
+ radix = 16;
+ value = value.substring(2);
+ }
+ try {
+ intValue = Integer.parseInt(value, radix);
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "invalid number for field " + name, e);
+ continue;
+ }
+ if ("vendor-id".equals(name)) {
+ vendorId = intValue;
+ } else if ("product-id".equals(name)) {
+ productId = intValue;
+ } else if ("class".equals(name)) {
+ deviceClass = intValue;
+ } else if ("subclass".equals(name)) {
+ deviceSubclass = intValue;
+ } else if ("protocol".equals(name)) {
+ deviceProtocol = intValue;
+ }
+ }
+ }
+ return new DeviceFilter(vendorId, productId,
+ deviceClass, deviceSubclass, deviceProtocol,
+ manufacturerName, productName, serialNumber);
+ }
+
+ public void write(XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, "usb-device");
+ if (mVendorId != -1) {
+ serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
+ }
+ if (mProductId != -1) {
+ serializer.attribute(null, "product-id", Integer.toString(mProductId));
+ }
+ if (mClass != -1) {
+ serializer.attribute(null, "class", Integer.toString(mClass));
+ }
+ if (mSubclass != -1) {
+ serializer.attribute(null, "subclass", Integer.toString(mSubclass));
+ }
+ if (mProtocol != -1) {
+ serializer.attribute(null, "protocol", Integer.toString(mProtocol));
+ }
+ if (mManufacturerName != null) {
+ serializer.attribute(null, "manufacturer-name", mManufacturerName);
+ }
+ if (mProductName != null) {
+ serializer.attribute(null, "product-name", mProductName);
+ }
+ if (mSerialNumber != null) {
+ serializer.attribute(null, "serial-number", mSerialNumber);
+ }
+ serializer.endTag(null, "usb-device");
+ }
+
+ private boolean matches(int clasz, int subclass, int protocol) {
+ return ((mClass == -1 || clasz == mClass) &&
+ (mSubclass == -1 || subclass == mSubclass) &&
+ (mProtocol == -1 || protocol == mProtocol));
+ }
+
+ public boolean matches(UsbDevice device) {
+ if (mVendorId != -1 && device.getVendorId() != mVendorId) return false;
+ if (mProductId != -1 && device.getProductId() != mProductId) return false;
+ if (mManufacturerName != null && device.getManufacturerName() == null) return false;
+ if (mProductName != null && device.getProductName() == null) return false;
+ if (mSerialNumber != null && device.getSerialNumber() == null) return false;
+ if (mManufacturerName != null && device.getManufacturerName() != null &&
+ !mManufacturerName.equals(device.getManufacturerName())) return false;
+ if (mProductName != null && device.getProductName() != null &&
+ !mProductName.equals(device.getProductName())) return false;
+ if (mSerialNumber != null && device.getSerialNumber() != null &&
+ !mSerialNumber.equals(device.getSerialNumber())) return false;
+
+ // check device class/subclass/protocol
+ if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
+ device.getDeviceProtocol())) return true;
+
+ // if device doesn't match, check the interfaces
+ int count = device.getInterfaceCount();
+ for (int i = 0; i < count; i++) {
+ UsbInterface intf = device.getInterface(i);
+ if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
+ intf.getInterfaceProtocol())) return true;
+ }
+
+ return false;
+ }
+
+ public boolean matches(DeviceFilter f) {
+ if (mVendorId != -1 && f.mVendorId != mVendorId) return false;
+ if (mProductId != -1 && f.mProductId != mProductId) return false;
+ if (f.mManufacturerName != null && mManufacturerName == null) return false;
+ if (f.mProductName != null && mProductName == null) return false;
+ if (f.mSerialNumber != null && mSerialNumber == null) return false;
+ if (mManufacturerName != null && f.mManufacturerName != null &&
+ !mManufacturerName.equals(f.mManufacturerName)) return false;
+ if (mProductName != null && f.mProductName != null &&
+ !mProductName.equals(f.mProductName)) return false;
+ if (mSerialNumber != null && f.mSerialNumber != null &&
+ !mSerialNumber.equals(f.mSerialNumber)) return false;
+
+ // check device class/subclass/protocol
+ return matches(f.mClass, f.mSubclass, f.mProtocol);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // can't compare if we have wildcard strings
+ if (mVendorId == -1 || mProductId == -1 ||
+ mClass == -1 || mSubclass == -1 || mProtocol == -1) {
+ return false;
+ }
+ if (obj instanceof DeviceFilter) {
+ DeviceFilter filter = (DeviceFilter)obj;
+
+ if (filter.mVendorId != mVendorId ||
+ filter.mProductId != mProductId ||
+ filter.mClass != mClass ||
+ filter.mSubclass != mSubclass ||
+ filter.mProtocol != mProtocol) {
+ return(false);
+ }
+ if ((filter.mManufacturerName != null &&
+ mManufacturerName == null) ||
+ (filter.mManufacturerName == null &&
+ mManufacturerName != null) ||
+ (filter.mProductName != null &&
+ mProductName == null) ||
+ (filter.mProductName == null &&
+ mProductName != null) ||
+ (filter.mSerialNumber != null &&
+ mSerialNumber == null) ||
+ (filter.mSerialNumber == null &&
+ mSerialNumber != null)) {
+ return(false);
+ }
+ if ((filter.mManufacturerName != null &&
+ mManufacturerName != null &&
+ !mManufacturerName.equals(filter.mManufacturerName)) ||
+ (filter.mProductName != null &&
+ mProductName != null &&
+ !mProductName.equals(filter.mProductName)) ||
+ (filter.mSerialNumber != null &&
+ mSerialNumber != null &&
+ !mSerialNumber.equals(filter.mSerialNumber))) {
+ return(false);
+ }
+ return(true);
+ }
+ if (obj instanceof UsbDevice) {
+ UsbDevice device = (UsbDevice)obj;
+ if (device.getVendorId() != mVendorId ||
+ device.getProductId() != mProductId ||
+ device.getDeviceClass() != mClass ||
+ device.getDeviceSubclass() != mSubclass ||
+ device.getDeviceProtocol() != mProtocol) {
+ return(false);
+ }
+ if ((mManufacturerName != null && device.getManufacturerName() == null) ||
+ (mManufacturerName == null && device.getManufacturerName() != null) ||
+ (mProductName != null && device.getProductName() == null) ||
+ (mProductName == null && device.getProductName() != null) ||
+ (mSerialNumber != null && device.getSerialNumber() == null) ||
+ (mSerialNumber == null && device.getSerialNumber() != null)) {
+ return(false);
+ }
+ if ((device.getManufacturerName() != null &&
+ !mManufacturerName.equals(device.getManufacturerName())) ||
+ (device.getProductName() != null &&
+ !mProductName.equals(device.getProductName())) ||
+ (device.getSerialNumber() != null &&
+ !mSerialNumber.equals(device.getSerialNumber()))) {
+ return(false);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (((mVendorId << 16) | mProductId) ^
+ ((mClass << 16) | (mSubclass << 8) | mProtocol));
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
+ ",mClass=" + mClass + ",mSubclass=" + mSubclass +
+ ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
+ ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
+ "]";
+ }
+ }
+
+ // This class is used to describe a USB accessory.
+ // When used in HashMaps all values must be specified,
+ // but wildcards can be used for any of the fields in
+ // the package meta-data.
+ private static class AccessoryFilter {
+ // USB accessory manufacturer (or null for unspecified)
+ public final String mManufacturer;
+ // USB accessory model (or null for unspecified)
+ public final String mModel;
+ // USB accessory version (or null for unspecified)
+ public final String mVersion;
+
+ public AccessoryFilter(String manufacturer, String model, String version) {
+ mManufacturer = manufacturer;
+ mModel = model;
+ mVersion = version;
+ }
+
+ public AccessoryFilter(UsbAccessory accessory) {
+ mManufacturer = accessory.getManufacturer();
+ mModel = accessory.getModel();
+ mVersion = accessory.getVersion();
+ }
+
+ public static AccessoryFilter read(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String manufacturer = null;
+ String model = null;
+ String version = null;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+
+ if ("manufacturer".equals(name)) {
+ manufacturer = value;
+ } else if ("model".equals(name)) {
+ model = value;
+ } else if ("version".equals(name)) {
+ version = value;
+ }
+ }
+ return new AccessoryFilter(manufacturer, model, version);
+ }
+
+ public void write(XmlSerializer serializer)throws IOException {
+ serializer.startTag(null, "usb-accessory");
+ if (mManufacturer != null) {
+ serializer.attribute(null, "manufacturer", mManufacturer);
+ }
+ if (mModel != null) {
+ serializer.attribute(null, "model", mModel);
+ }
+ if (mVersion != null) {
+ serializer.attribute(null, "version", mVersion);
+ }
+ serializer.endTag(null, "usb-accessory");
+ }
+
+ public boolean matches(UsbAccessory acc) {
+ if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
+ if (mModel != null && !acc.getModel().equals(mModel)) return false;
+ if (mVersion != null && !acc.getVersion().equals(mVersion)) return false;
+ return true;
+ }
+
+ public boolean matches(AccessoryFilter f) {
+ if (mManufacturer != null && !f.mManufacturer.equals(mManufacturer)) return false;
+ if (mModel != null && !f.mModel.equals(mModel)) return false;
+ if (mVersion != null && !f.mVersion.equals(mVersion)) return false;
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // can't compare if we have wildcard strings
+ if (mManufacturer == null || mModel == null || mVersion == null) {
+ return false;
+ }
+ if (obj instanceof AccessoryFilter) {
+ AccessoryFilter filter = (AccessoryFilter)obj;
+ return (mManufacturer.equals(filter.mManufacturer) &&
+ mModel.equals(filter.mModel) &&
+ mVersion.equals(filter.mVersion));
+ }
+ if (obj instanceof UsbAccessory) {
+ UsbAccessory accessory = (UsbAccessory)obj;
+ return (mManufacturer.equals(accessory.getManufacturer()) &&
+ mModel.equals(accessory.getModel()) &&
+ mVersion.equals(accessory.getVersion()));
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
+ (mModel == null ? 0 : mModel.hashCode()) ^
+ (mVersion == null ? 0 : mVersion.hashCode()));
+ }
+
+ @Override
+ public String toString() {
+ return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
+ "\", mModel=\"" + mModel +
+ "\", mVersion=\"" + mVersion + "\"]";
+ }
+ }
+
+ private class MyPackageMonitor extends PackageMonitor {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ handlePackageUpdate(packageName);
+ }
+
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ handlePackageUpdate(packageName);
+ return false;
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ clearDefaults(packageName, UserHandle.getUserHandleForUid(uid));
+ }
+ }
+
+ MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
+
+ private final MtpNotificationManager mMtpNotificationManager;
+
+ /**
+ * Create new settings manager for a profile group.
+ *
+ * @param context The context of the service
+ * @param user The parent profile
+ * @param settingsManager The settings manager of the service
+ */
+ UsbProfileGroupSettingsManager(@NonNull Context context, @NonNull UserHandle user,
+ @NonNull UsbSettingsManager settingsManager) {
+ if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
+
+ Context parentUserContext;
+ try {
+ parentUserContext = context.createPackageContextAsUser("android", 0, user);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException("Missing android package");
+ }
+
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mSettingsManager = settingsManager;
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+
+ mParentUser = user;
+ mSettingsFile = new AtomicFile(new File(
+ Environment.getUserSystemDirectory(user.getIdentifier()),
+ "usb_device_manager.xml"));
+
+ mDisablePermissionDialogs = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_disableUsbPermissionDialogs);
+
+ synchronized (mLock) {
+ if (UserHandle.SYSTEM.equals(user)) {
+ upgradeSingleUserLocked();
+ }
+ readSettingsLocked();
+ }
+
+ mPackageMonitor.register(context, null, true);
+ mMtpNotificationManager = new MtpNotificationManager(
+ parentUserContext,
+ new MtpNotificationManager.OnOpenInAppListener() {
+ @Override
+ public void onOpenInApp(UsbDevice device) {
+ resolveActivity(createDeviceAttachedIntent(device), device);
+ }
+ });
+ }
+
+ private void readPreference(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String packageName = null;
+
+ // If not set, assume it to be the parent profile
+ UserHandle user = mParentUser;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ if ("package".equals(parser.getAttributeName(i))) {
+ packageName = parser.getAttributeValue(i);
+ }
+ if ("user".equals(parser.getAttributeName(i))) {
+ // Might return null if user is not known anymore
+ user = mUserManager
+ .getUserForSerialNumber(Integer.parseInt(parser.getAttributeValue(i)));
+ }
+ }
+
+ XmlUtils.nextElement(parser);
+ if ("usb-device".equals(parser.getName())) {
+ DeviceFilter filter = DeviceFilter.read(parser);
+ if (user != null) {
+ mDevicePreferenceMap.put(filter, new UserPackage(packageName, user));
+ }
+ } else if ("usb-accessory".equals(parser.getName())) {
+ AccessoryFilter filter = AccessoryFilter.read(parser);
+ if (user != null) {
+ mAccessoryPreferenceMap.put(filter, new UserPackage(packageName, user));
+ }
+ }
+ XmlUtils.nextElement(parser);
+ }
+
+ /**
+ * Upgrade any single-user settings from {@link #sSingleUserSettingsFile}.
+ * Should only by called by owner.
+ */
+ private void upgradeSingleUserLocked() {
+ if (sSingleUserSettingsFile.exists()) {
+ mDevicePreferenceMap.clear();
+ mAccessoryPreferenceMap.clear();
+
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(sSingleUserSettingsFile);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, StandardCharsets.UTF_8.name());
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ final String tagName = parser.getName();
+ if ("preference".equals(tagName)) {
+ readPreference(parser);
+ } else {
+ XmlUtils.nextElement(parser);
+ }
+ }
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to read single-user settings", e);
+ } catch (XmlPullParserException e) {
+ Log.wtf(TAG, "Failed to read single-user settings", e);
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+
+ writeSettingsLocked();
+
+ // Success or failure, we delete single-user file
+ sSingleUserSettingsFile.delete();
+ }
+ }
+
+ private void readSettingsLocked() {
+ if (DEBUG) Slog.v(TAG, "readSettingsLocked()");
+
+ mDevicePreferenceMap.clear();
+ mAccessoryPreferenceMap.clear();
+
+ FileInputStream stream = null;
+ try {
+ stream = mSettingsFile.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if ("preference".equals(tagName)) {
+ readPreference(parser);
+ } else {
+ XmlUtils.nextElement(parser);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ if (DEBUG) Slog.d(TAG, "settings file not found");
+ } catch (Exception e) {
+ Slog.e(TAG, "error reading settings file, deleting to start fresh", e);
+ mSettingsFile.delete();
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ }
+
+ private void writeSettingsLocked() {
+ if (DEBUG) Slog.v(TAG, "writeSettingsLocked()");
+
+ FileOutputStream fos = null;
+ try {
+ fos = mSettingsFile.startWrite();
+
+ FastXmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(fos, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.startTag(null, "settings");
+
+ for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
+ serializer.startTag(null, "preference");
+ serializer.attribute(null, "package", mDevicePreferenceMap.get(filter).packageName);
+ serializer.attribute(null, "user",
+ String.valueOf(getSerial(mDevicePreferenceMap.get(filter).user)));
+ filter.write(serializer);
+ serializer.endTag(null, "preference");
+ }
+
+ for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
+ serializer.startTag(null, "preference");
+ serializer.attribute(null, "package",
+ mAccessoryPreferenceMap.get(filter).packageName);
+ serializer.attribute(null, "user",
+ String.valueOf(getSerial(mAccessoryPreferenceMap.get(filter).user)));
+ filter.write(serializer);
+ serializer.endTag(null, "preference");
+ }
+
+ serializer.endTag(null, "settings");
+ serializer.endDocument();
+
+ mSettingsFile.finishWrite(fos);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write settings", e);
+ if (fos != null) {
+ mSettingsFile.failWrite(fos);
+ }
+ }
+ }
+
+ // Checks to see if a package matches a device or accessory.
+ // Only one of device and accessory should be non-null.
+ private boolean packageMatchesLocked(ResolveInfo info, String metaDataName,
+ UsbDevice device, UsbAccessory accessory) {
+ if (info.getComponentInfo().name.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
+ return true;
+ }
+
+ ActivityInfo ai = info.activityInfo;
+
+ XmlResourceParser parser = null;
+ try {
+ parser = ai.loadXmlMetaData(mPackageManager, metaDataName);
+ if (parser == null) {
+ Slog.w(TAG, "no meta-data for " + info);
+ return false;
+ }
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (device != null && "usb-device".equals(tagName)) {
+ DeviceFilter filter = DeviceFilter.read(parser);
+ if (filter.matches(device)) {
+ return true;
+ }
+ }
+ else if (accessory != null && "usb-accessory".equals(tagName)) {
+ AccessoryFilter filter = AccessoryFilter.read(parser);
+ if (filter.matches(accessory)) {
+ return true;
+ }
+ }
+ XmlUtils.nextElement(parser);
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to load component info " + info.toString(), e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ return false;
+ }
+
+ /**
+ * Resolve all activities that match an intent for all profiles of this group.
+ *
+ * @param intent The intent to resolve
+ *
+ * @return The {@link ResolveInfo}s for all profiles of the group
+ */
+ private @NonNull ArrayList<ResolveInfo> queryIntentActivitiesForAllProfiles(
+ @NonNull Intent intent) {
+ List<UserInfo> profiles = mUserManager.getEnabledProfiles(mParentUser.getIdentifier());
+
+ ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
+ int numProfiles = profiles.size();
+ for (int i = 0; i < numProfiles; i++) {
+ resolveInfos.addAll(mPackageManager.queryIntentActivitiesAsUser(intent,
+ PackageManager.GET_META_DATA, profiles.get(i).id));
+ }
+
+ return resolveInfos;
+ }
+
+ private final ArrayList<ResolveInfo> getDeviceMatchesLocked(UsbDevice device, Intent intent) {
+ ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
+ List<ResolveInfo> resolveInfos = queryIntentActivitiesForAllProfiles(intent);
+ int count = resolveInfos.size();
+ for (int i = 0; i < count; i++) {
+ ResolveInfo resolveInfo = resolveInfos.get(i);
+ if (packageMatchesLocked(resolveInfo, intent.getAction(), device, null)) {
+ matches.add(resolveInfo);
+ }
+ }
+ return matches;
+ }
+
+ private final ArrayList<ResolveInfo> getAccessoryMatchesLocked(
+ UsbAccessory accessory, Intent intent) {
+ ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
+ List<ResolveInfo> resolveInfos = queryIntentActivitiesForAllProfiles(intent);
+ int count = resolveInfos.size();
+ for (int i = 0; i < count; i++) {
+ ResolveInfo resolveInfo = resolveInfos.get(i);
+ if (packageMatchesLocked(resolveInfo, intent.getAction(), null, accessory)) {
+ matches.add(resolveInfo);
+ }
+ }
+ return matches;
+ }
+
+ public void deviceAttached(UsbDevice device) {
+ final Intent intent = createDeviceAttachedIntent(device);
+
+ // Send broadcast to running activities with registered intent
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+ if (MtpNotificationManager.shouldShowNotification(mPackageManager, device)) {
+ // Show notification if the device is MTP storage.
+ mMtpNotificationManager.showNotification(device);
+ } else {
+ resolveActivity(intent, device);
+ }
+ }
+
+ private void resolveActivity(Intent intent, UsbDevice device) {
+ ArrayList<ResolveInfo> matches;
+ String defaultPackage = null;
+ UserHandle user = null;
+ synchronized (mLock) {
+ matches = getDeviceMatchesLocked(device, intent);
+ // Launch our default activity directly, if we have one.
+ // Otherwise we will start the UsbResolverActivity to allow the user to choose.
+ UserPackage userPackage = mDevicePreferenceMap.get(new DeviceFilter(device));
+ if (userPackage != null) {
+ defaultPackage = userPackage.packageName;
+ user = userPackage.user;
+ }
+ }
+
+ // Start activity with registered intent
+ resolveActivity(intent, matches, defaultPackage, user, device, null);
+ }
+
+ public void deviceAttachedForFixedHandler(UsbDevice device, ComponentName component) {
+ final Intent intent = createDeviceAttachedIntent(device);
+
+ // Send broadcast to running activity with registered intent
+ mContext.sendBroadcast(intent);
+
+ ApplicationInfo appInfo;
+ try {
+ appInfo = mPackageManager.getApplicationInfo(component.getPackageName(), 0);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Default USB handling package not found: " + component.getPackageName());
+ return;
+ }
+
+ mSettingsManager.getSettingsForUser(UserHandle.getUserId(appInfo.uid))
+ .grantDevicePermission(device, appInfo.uid);
+
+ Intent activityIntent = new Intent(intent);
+ activityIntent.setComponent(component);
+ try {
+ mContext.startActivityAsUser(activityIntent, mParentUser);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "unable to start activity " + activityIntent);
+ }
+ }
+
+ /**
+ * Remove notifications for a usb device.
+ *
+ * @param device The device the notifications are for.
+ */
+ void usbDeviceRemoved(@NonNull UsbDevice device) {
+ mMtpNotificationManager.hideNotification(device.getDeviceId());
+ }
+
+ public void accessoryAttached(UsbAccessory accessory) {
+ Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
+ intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ ArrayList<ResolveInfo> matches;
+ String defaultPackage = null;
+ UserHandle user = null;
+ synchronized (mLock) {
+ matches = getAccessoryMatchesLocked(accessory, intent);
+ // Launch our default activity directly, if we have one.
+ // Otherwise we will start the UsbResolverActivity to allow the user to choose.
+ UserPackage userPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory));
+ if (userPackage != null) {
+ defaultPackage = userPackage.packageName;
+ user = userPackage.user;
+ }
+ }
+
+ resolveActivity(intent, matches, defaultPackage, user, null, accessory);
+ }
+
+ /**
+ * Start the appropriate package when an device/accessory got attached.
+ *
+ * @param intent The intent to start the package
+ * @param matches The available resolutions of the intent
+ * @param defaultPackage The default package for the device (if set)
+ * @param defaultUser The user of the default package (if package is set)
+ * @param device The device if a device was attached
+ * @param accessory The accessory if a device was attached
+ */
+ private void resolveActivity(@NonNull Intent intent, @NonNull ArrayList<ResolveInfo> matches,
+ @Nullable String defaultPackage, @Nullable UserHandle defaultUser,
+ @Nullable UsbDevice device, @Nullable UsbAccessory accessory) {
+ int count = matches.size();
+
+ // don't show the resolver activity if there are no choices available
+ if (count == 0) {
+ if (accessory != null) {
+ String uri = accessory.getUri();
+ if (uri != null && uri.length() > 0) {
+ // display URI to user
+ Intent dialogIntent = new Intent();
+ dialogIntent.setClassName("com.android.systemui",
+ "com.android.systemui.usb.UsbAccessoryUriActivity");
+ dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+ dialogIntent.putExtra("uri", uri);
+ try {
+ mContext.startActivityAsUser(dialogIntent, mParentUser);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "unable to start UsbAccessoryUriActivity");
+ }
+ }
+ }
+
+ // do nothing
+ return;
+ }
+
+ ResolveInfo defaultRI = null;
+ if (count == 1 && defaultPackage == null) {
+ // Check to see if our single choice is on the system partition.
+ // If so, treat it as our default without calling UsbResolverActivity
+ ResolveInfo rInfo = matches.get(0);
+ if (rInfo.activityInfo != null &&
+ rInfo.activityInfo.applicationInfo != null &&
+ (rInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ defaultRI = rInfo;
+ }
+
+ if (mDisablePermissionDialogs) {
+ // bypass dialog and launch the only matching activity
+ rInfo = matches.get(0);
+ if (rInfo.activityInfo != null) {
+ defaultPackage = rInfo.activityInfo.packageName;
+ defaultUser = UserHandle.getUserHandleForUid(
+ rInfo.activityInfo.applicationInfo.uid);
+ }
+ }
+ }
+
+ if (defaultRI == null && defaultPackage != null) {
+ // look for default activity
+ for (int i = 0; i < count; i++) {
+ ResolveInfo rInfo = matches.get(i);
+ if (rInfo.activityInfo != null &&
+ defaultPackage.equals(rInfo.activityInfo.packageName) &&
+ defaultUser.getIdentifier() ==
+ UserHandle.getUserId(rInfo.activityInfo.applicationInfo.uid)) {
+ defaultRI = rInfo;
+ break;
+ }
+ }
+ }
+
+ if (defaultRI != null) {
+ UsbUserSettingsManager defaultRIUserSettings = mSettingsManager.getSettingsForUser(
+ UserHandle.getUserId(defaultRI.activityInfo.applicationInfo.uid));
+ // grant permission for default activity
+ if (device != null) {
+ defaultRIUserSettings.
+ grantDevicePermission(device, defaultRI.activityInfo.applicationInfo.uid);
+ } else if (accessory != null) {
+ defaultRIUserSettings.grantAccessoryPermission(accessory,
+ defaultRI.activityInfo.applicationInfo.uid);
+ }
+
+ // start default activity directly
+ try {
+ intent.setComponent(
+ new ComponentName(defaultRI.activityInfo.packageName,
+ defaultRI.activityInfo.name));
+
+ UserHandle user = UserHandle.getUserHandleForUid(
+ defaultRI.activityInfo.applicationInfo.uid);
+ mContext.startActivityAsUser(intent, user);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "startActivity failed", e);
+ }
+ } else {
+ Intent resolverIntent = new Intent();
+ resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ UserHandle user;
+
+ if (count == 1) {
+ ResolveInfo rInfo = matches.get(0);
+
+ // start UsbConfirmActivity if there is only one choice
+ resolverIntent.setClassName("com.android.systemui",
+ "com.android.systemui.usb.UsbConfirmActivity");
+ resolverIntent.putExtra("rinfo", rInfo);
+ user = UserHandle.getUserHandleForUid(rInfo.activityInfo.applicationInfo.uid);
+
+ if (device != null) {
+ resolverIntent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ } else {
+ resolverIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+ }
+ } else {
+ user = mParentUser;
+
+ // start UsbResolverActivity so user can choose an activity
+ resolverIntent.setClassName("com.android.systemui",
+ "com.android.systemui.usb.UsbResolverActivity");
+ resolverIntent.putParcelableArrayListExtra("rlist", matches);
+ resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
+ }
+ try {
+ mContext.startActivityAsUser(resolverIntent, user);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "unable to start activity " + resolverIntent, e);
+ }
+ }
+ }
+
+ private boolean clearCompatibleMatchesLocked(String packageName, DeviceFilter filter) {
+ boolean changed = false;
+ for (DeviceFilter test : mDevicePreferenceMap.keySet()) {
+ if (filter.matches(test)) {
+ mDevicePreferenceMap.remove(test);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ private boolean clearCompatibleMatchesLocked(String packageName, AccessoryFilter filter) {
+ boolean changed = false;
+ for (AccessoryFilter test : mAccessoryPreferenceMap.keySet()) {
+ if (filter.matches(test)) {
+ mAccessoryPreferenceMap.remove(test);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ private boolean handlePackageUpdateLocked(String packageName, ActivityInfo aInfo,
+ String metaDataName) {
+ XmlResourceParser parser = null;
+ boolean changed = false;
+
+ try {
+ parser = aInfo.loadXmlMetaData(mPackageManager, metaDataName);
+ if (parser == null) return false;
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if ("usb-device".equals(tagName)) {
+ DeviceFilter filter = DeviceFilter.read(parser);
+ if (clearCompatibleMatchesLocked(packageName, filter)) {
+ changed = true;
+ }
+ }
+ else if ("usb-accessory".equals(tagName)) {
+ AccessoryFilter filter = AccessoryFilter.read(parser);
+ if (clearCompatibleMatchesLocked(packageName, filter)) {
+ changed = true;
+ }
+ }
+ XmlUtils.nextElement(parser);
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to load component info " + aInfo.toString(), e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ return changed;
+ }
+
+ // Check to see if the package supports any USB devices or accessories.
+ // If so, clear any non-matching preferences for matching devices/accessories.
+ private void handlePackageUpdate(String packageName) {
+ synchronized (mLock) {
+ PackageInfo info;
+ boolean changed = false;
+
+ try {
+ info = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
+ return;
+ }
+
+ ActivityInfo[] activities = info.activities;
+ if (activities == null) return;
+ for (int i = 0; i < activities.length; i++) {
+ // check for meta-data, both for devices and accessories
+ if (handlePackageUpdateLocked(packageName, activities[i],
+ UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+ changed = true;
+ }
+ if (handlePackageUpdateLocked(packageName, activities[i],
+ UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ writeSettingsLocked();
+ }
+ }
+ }
+
+ /**
+ * Get the serial number for a user handle.
+ *
+ * @param user The user handle
+ *
+ * @return The serial number
+ */
+ private int getSerial(@NonNull UserHandle user) {
+ return mUserManager.getUserSerialNumber(user.getIdentifier());
+ }
+
+ /**
+ * Set a package as default handler for a device.
+ *
+ * @param device The device that should be handled by default
+ * @param packageName The default handler package
+ * @param user The user the package belongs to
+ */
+ void setDevicePackage(@NonNull UsbDevice device, @Nullable String packageName,
+ @Nullable UserHandle user) {
+ DeviceFilter filter = new DeviceFilter(device);
+ boolean changed = false;
+ synchronized (mLock) {
+ if (packageName == null) {
+ changed = (mDevicePreferenceMap.remove(filter) != null);
+ } else {
+ changed = !packageName.equals(mDevicePreferenceMap.get(filter));
+ if (changed) {
+ mDevicePreferenceMap.put(filter, new UserPackage(packageName, user));
+ }
+ }
+ if (changed) {
+ writeSettingsLocked();
+ }
+ }
+ }
+
+ /**
+ * Set a package as default handler for a accessory.
+ *
+ * @param accessory The accessory that should be handled by default
+ * @param packageName The default handler package
+ * @param user The user the package belongs to
+ */
+ void setAccessoryPackage(@NonNull UsbAccessory accessory, @Nullable String packageName,
+ @Nullable UserHandle user) {
+ AccessoryFilter filter = new AccessoryFilter(accessory);
+ boolean changed = false;
+ synchronized (mLock) {
+ if (packageName == null) {
+ changed = (mAccessoryPreferenceMap.remove(filter) != null);
+ } else {
+ changed = !packageName.equals(mAccessoryPreferenceMap.get(filter));
+ if (changed) {
+ mAccessoryPreferenceMap.put(filter, new UserPackage(packageName, user));
+ }
+ }
+ if (changed) {
+ writeSettingsLocked();
+ }
+ }
+ }
+
+ /**
+ * Check if a package has is the default handler for any usb device or accessory.
+ *
+ * @param packageName The package name
+ * @param user The user the package belongs to
+ *
+ * @return {@code true} iff the package is default for any usb device or accessory
+ */
+ boolean hasDefaults(@NonNull String packageName, @NonNull UserHandle user) {
+ UserPackage userPackage = new UserPackage(packageName, user);
+ synchronized (mLock) {
+ if (mDevicePreferenceMap.values().contains(userPackage)) return true;
+ if (mAccessoryPreferenceMap.values().contains(userPackage)) return true;
+ return false;
+ }
+ }
+
+ /**
+ * Clear defaults for a package from any preference.
+ *
+ * @param packageName The package to remove
+ * @param user The user the package belongs to
+ */
+ void clearDefaults(@NonNull String packageName, @NonNull UserHandle user) {
+ UserPackage userPackage = new UserPackage(packageName, user);
+
+ synchronized (mLock) {
+ if (clearPackageDefaultsLocked(userPackage)) {
+ writeSettingsLocked();
+ }
+ }
+ }
+
+ /**
+ * Clear defaults for a package from any preference (does not persist).
+ *
+ * @param userPackage The package to remove
+ *
+ * @return {@code true} iff at least one preference was cleared
+ */
+ private boolean clearPackageDefaultsLocked(@NonNull UserPackage userPackage) {
+ boolean cleared = false;
+ synchronized (mLock) {
+ if (mDevicePreferenceMap.containsValue(userPackage)) {
+ // make a copy of the key set to avoid ConcurrentModificationException
+ Object[] keys = mDevicePreferenceMap.keySet().toArray();
+ for (int i = 0; i < keys.length; i++) {
+ Object key = keys[i];
+ if (userPackage.equals(mDevicePreferenceMap.get(key))) {
+ mDevicePreferenceMap.remove(key);
+ cleared = true;
+ }
+ }
+ }
+ if (mAccessoryPreferenceMap.containsValue(userPackage)) {
+ // make a copy of the key set to avoid ConcurrentModificationException
+ Object[] keys = mAccessoryPreferenceMap.keySet().toArray();
+ for (int i = 0; i < keys.length; i++) {
+ Object key = keys[i];
+ if (userPackage.equals(mAccessoryPreferenceMap.get(key))) {
+ mAccessoryPreferenceMap.remove(key);
+ cleared = true;
+ }
+ }
+ }
+ return cleared;
+ }
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ pw.println("Device preferences:");
+ for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
+ pw.println(" " + filter + ": " + mDevicePreferenceMap.get(filter));
+ }
+ pw.println("Accessory preferences:");
+ for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
+ pw.println(" " + filter + ": " + mAccessoryPreferenceMap.get(filter));
+ }
+ }
+ }
+
+ private static Intent createDeviceAttachedIntent(UsbDevice device) {
+ Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 81ac2dd..02c7214 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -16,6 +16,7 @@
package com.android.server.usb;
+import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -36,7 +37,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
@@ -75,57 +75,65 @@
mUsbService.bootCompleted();
}
}
+
+ @Override
+ public void onSwitchUser(int newUserId) {
+ mUsbService.onSwitchUser(newUserId);
+ }
+
+ @Override
+ public void onStopUser(int userHandle) {
+ mUsbService.onStopUser(userHandle);
+ }
}
private static final String TAG = "UsbService";
private final Context mContext;
+ private final UserManager mUserManager;
private UsbDeviceManager mDeviceManager;
private UsbHostManager mHostManager;
private UsbPortManager mPortManager;
private final UsbAlsaManager mAlsaManager;
+ private final UsbSettingsManager mSettingsManager;
+
+ /**
+ * The user id of the current user. There might be several profiles (with separate user ids)
+ * per user.
+ */
+ @GuardedBy("mLock")
+ private @UserIdInt int mCurrentUserId;
+
private final Object mLock = new Object();
- /** Map from {@link UserHandle} to {@link UsbSettingsManager} */
- @GuardedBy("mLock")
- private final SparseArray<UsbSettingsManager>
- mSettingsByUser = new SparseArray<UsbSettingsManager>();
-
- private UsbSettingsManager getSettingsForUser(int userId) {
- synchronized (mLock) {
- UsbSettingsManager settings = mSettingsByUser.get(userId);
- if (settings == null) {
- settings = new UsbSettingsManager(mContext, new UserHandle(userId));
- mSettingsByUser.put(userId, settings);
- }
- return settings;
- }
+ private UsbUserSettingsManager getSettingsForUser(@UserIdInt int userIdInt) {
+ return mSettingsManager.getSettingsForUser(userIdInt);
}
public UsbService(Context context) {
mContext = context;
+ mUserManager = context.getSystemService(UserManager.class);
+ mSettingsManager = new UsbSettingsManager(context);
mAlsaManager = new UsbAlsaManager(context);
final PackageManager pm = mContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
- mHostManager = new UsbHostManager(context, mAlsaManager);
+ mHostManager = new UsbHostManager(context, mAlsaManager, mSettingsManager);
}
if (new File("/sys/class/android_usb").exists()) {
- mDeviceManager = new UsbDeviceManager(context, mAlsaManager);
+ mDeviceManager = new UsbDeviceManager(context, mAlsaManager, mSettingsManager);
}
if (mHostManager != null || mDeviceManager != null) {
mPortManager = new UsbPortManager(context);
}
- setCurrentUser(UserHandle.USER_SYSTEM);
+ onSwitchUser(UserHandle.USER_SYSTEM);
final IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_USER_STOPPED);
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mContext.registerReceiver(mReceiver, filter, null, null);
}
@@ -133,15 +141,8 @@
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
final String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- setCurrentUser(userId);
- } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
- synchronized (mLock) {
- mSettingsByUser.remove(userId);
- }
- } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
+ if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
.equals(action)) {
if (mDeviceManager != null) {
mDeviceManager.updateUserRestrictions();
@@ -150,14 +151,36 @@
}
};
- private void setCurrentUser(int userId) {
- final UsbSettingsManager userSettings = getSettingsForUser(userId);
- if (mHostManager != null) {
- mHostManager.setCurrentSettings(userSettings);
+ /**
+ * Set new {@link #mCurrentUserId} and propagate it to other modules.
+ *
+ * @param newUserId The user id of the new current user.
+ */
+ private void onSwitchUser(@UserIdInt int newUserId) {
+ synchronized (mLock) {
+ mCurrentUserId = newUserId;
+
+ // The following two modules need to know about the current profile group. If they need
+ // to distinguish by profile of the user, the id has to be passed in the call to the
+ // module.
+ UsbProfileGroupSettingsManager settings =
+ mSettingsManager.getSettingsForProfileGroup(UserHandle.of(newUserId));
+ if (mHostManager != null) {
+ mHostManager.setCurrentUserSettings(settings);
+ }
+ if (mDeviceManager != null) {
+ mDeviceManager.setCurrentUser(newUserId, settings);
+ }
}
- if (mDeviceManager != null) {
- mDeviceManager.setCurrentUser(userId, userSettings);
- }
+ }
+
+ /**
+ * Execute operations when a user is stopped.
+ *
+ * @param stoppedUserId The id of the used that is stopped
+ */
+ private void onStopUser(@UserIdInt int stoppedUserId) {
+ mSettingsManager.remove(stoppedUserId);
}
public void systemReady() {
@@ -188,14 +211,45 @@
}
}
+ /**
+ * Check if the calling user is in the same profile group as the {@link #mCurrentUserId
+ * current user}.
+ *
+ * @return Iff the caller is in the current user's profile group
+ */
+ private boolean isCallerInCurrentUserProfileGroupLocked() {
+ int userIdInt = UserHandle.getCallingUserId();
+
+ long ident = clearCallingIdentity();
+ try {
+ return mUserManager.isSameProfileGroup(userIdInt, mCurrentUserId);
+ } finally {
+ restoreCallingIdentity(ident);
+ }
+ }
+
/* Opens the specified USB device (host mode) */
@Override
public ParcelFileDescriptor openDevice(String deviceName) {
+ ParcelFileDescriptor fd = null;
+
if (mHostManager != null) {
- return mHostManager.openDevice(deviceName);
- } else {
- return null;
+ synchronized (mLock) {
+ if (deviceName != null) {
+ int userIdInt = UserHandle.getCallingUserId();
+ boolean isCurrentUser = isCallerInCurrentUserProfileGroupLocked();
+
+ if (isCurrentUser) {
+ fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt));
+ } else {
+ Slog.w(TAG, "Cannot open " + deviceName + " for user " + userIdInt +
+ " as user is not active.");
+ }
+ }
+ }
}
+
+ return fd;
}
/* returns the currently attached USB accessory (device mode) */
@@ -212,22 +266,43 @@
@Override
public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
if (mDeviceManager != null) {
- return mDeviceManager.openAccessory(accessory);
- } else {
- return null;
+ int userIdInt = UserHandle.getCallingUserId();
+
+ synchronized (mLock) {
+ boolean isCurrentUser = isCallerInCurrentUserProfileGroupLocked();
+
+ if (isCurrentUser) {
+ return mDeviceManager.openAccessory(accessory, getSettingsForUser(userIdInt));
+ } else {
+ Slog.w(TAG, "Cannot open " + accessory + " for user " + userIdInt +
+ " as user is not active.");
+ }
+ }
}
+
+ return null;
}
@Override
public void setDevicePackage(UsbDevice device, String packageName, int userId) {
+ device = Preconditions.checkNotNull(device);
+
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
- getSettingsForUser(userId).setDevicePackage(device, packageName);
+
+ UserHandle user = UserHandle.of(userId);
+ mSettingsManager.getSettingsForProfileGroup(user).setDevicePackage(device, packageName,
+ user);
}
@Override
public void setAccessoryPackage(UsbAccessory accessory, String packageName, int userId) {
+ accessory = Preconditions.checkNotNull(accessory);
+
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
- getSettingsForUser(userId).setAccessoryPackage(accessory, packageName);
+
+ UserHandle user = UserHandle.of(userId);
+ mSettingsManager.getSettingsForProfileGroup(user).setAccessoryPackage(accessory,
+ packageName, user);
}
@Override
@@ -271,14 +346,22 @@
@Override
public boolean hasDefaults(String packageName, int userId) {
+ packageName = Preconditions.checkStringNotEmpty(packageName);
+
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
- return getSettingsForUser(userId).hasDefaults(packageName);
+
+ UserHandle user = UserHandle.of(userId);
+ return mSettingsManager.getSettingsForProfileGroup(user).hasDefaults(packageName, user);
}
@Override
public void clearDefaults(String packageName, int userId) {
+ packageName = Preconditions.checkStringNotEmpty(packageName);
+
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
- getSettingsForUser(userId).clearDefaults(packageName);
+
+ UserHandle user = UserHandle.of(userId);
+ mSettingsManager.getSettingsForProfileGroup(user).clearDefaults(packageName, user);
}
@Override
@@ -388,8 +471,15 @@
@Override
public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
- if (mHostManager != null) {
- mHostManager.setUsbDeviceConnectionHandler(usbDeviceConnectionHandler);
+ synchronized (mLock) {
+ if (mCurrentUserId == UserHandle.getCallingUserId()) {
+ if (mHostManager != null) {
+ mHostManager.setUsbDeviceConnectionHandler(usbDeviceConnectionHandler);
+ }
+ } else {
+ throw new IllegalArgumentException("Only the current user can register a usb " +
+ "connection handler");
+ }
}
}
@@ -414,16 +504,7 @@
}
mAlsaManager.dump(pw);
- synchronized (mLock) {
- for (int i = 0; i < mSettingsByUser.size(); i++) {
- final int userId = mSettingsByUser.keyAt(i);
- final UsbSettingsManager settings = mSettingsByUser.valueAt(i);
- pw.println("Settings for user " + userId + ":");
- pw.increaseIndent();
- settings.dump(pw);
- pw.decreaseIndent();
- }
- }
+ mSettingsManager.dump(pw);
} else if (args.length == 4 && "set-port-roles".equals(args[0])) {
final String portId = args[1];
final int powerRole;
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index 6b9acf2..b251d26 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -1,1278 +1,195 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * 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.
+ * 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.usb;
-import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.res.XmlResourceParser;
+import android.content.pm.UserInfo;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
-import android.os.Binder;
-import android.os.Environment;
-import android.os.Process;
import android.os.UserHandle;
-import android.util.AtomicFile;
-import android.util.Log;
+import android.os.UserManager;
import android.util.Slog;
-import android.util.SparseBooleanArray;
-import android.util.Xml;
-
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.util.FastXmlSerializer;
+import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.XmlUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-import libcore.io.IoUtils;
-
+/**
+ * Maintains all {@link UsbUserSettingsManager} for all users.
+ */
class UsbSettingsManager {
- private static final String TAG = "UsbSettingsManager";
+ private static final String LOG_TAG = UsbSettingsManager.class.getSimpleName();
private static final boolean DEBUG = false;
- /** Legacy settings file, before multi-user */
- private static final File sSingleUserSettingsFile = new File(
- "/data/system/usb_device_manager.xml");
+ /** Context to be used by this module */
+ private final @NonNull Context mContext;
- private final UserHandle mUser;
- private final AtomicFile mSettingsFile;
- private final boolean mDisablePermissionDialogs;
+ /** Map from user id to {@link UsbUserSettingsManager} for the user */
+ @GuardedBy("mSettingsByUser")
+ private final SparseArray<UsbUserSettingsManager> mSettingsByUser = new SparseArray<>();
- private final Context mContext;
- private final Context mUserContext;
- private final PackageManager mPackageManager;
+ /**
+ * Map from the parent profile's user id to {@link UsbProfileGroupSettingsManager} for the
+ * group.
+ */
+ @GuardedBy("mSettingsByProfileGroup")
+ private final SparseArray<UsbProfileGroupSettingsManager> mSettingsByProfileGroup
+ = new SparseArray<>();
+ private UserManager mUserManager;
- // Temporary mapping USB device name to list of UIDs with permissions for the device
- private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
- new HashMap<String, SparseBooleanArray>();
- // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory
- private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
- new HashMap<UsbAccessory, SparseBooleanArray>();
- // Maps DeviceFilter to user preferred application package
- private final HashMap<DeviceFilter, String> mDevicePreferenceMap =
- new HashMap<DeviceFilter, String>();
- // Maps AccessoryFilter to user preferred application package
- private final HashMap<AccessoryFilter, String> mAccessoryPreferenceMap =
- new HashMap<AccessoryFilter, String>();
-
- private final Object mLock = new Object();
-
- // This class is used to describe a USB device.
- // When used in HashMaps all values must be specified,
- // but wildcards can be used for any of the fields in
- // the package meta-data.
- private static class DeviceFilter {
- // USB Vendor ID (or -1 for unspecified)
- public final int mVendorId;
- // USB Product ID (or -1 for unspecified)
- public final int mProductId;
- // USB device or interface class (or -1 for unspecified)
- public final int mClass;
- // USB device subclass (or -1 for unspecified)
- public final int mSubclass;
- // USB device protocol (or -1 for unspecified)
- public final int mProtocol;
- // USB device manufacturer name string (or null for unspecified)
- public final String mManufacturerName;
- // USB device product name string (or null for unspecified)
- public final String mProductName;
- // USB device serial number string (or null for unspecified)
- public final String mSerialNumber;
-
- public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
- String manufacturer, String product, String serialnum) {
- mVendorId = vid;
- mProductId = pid;
- mClass = clasz;
- mSubclass = subclass;
- mProtocol = protocol;
- mManufacturerName = manufacturer;
- mProductName = product;
- mSerialNumber = serialnum;
- }
-
- public DeviceFilter(UsbDevice device) {
- mVendorId = device.getVendorId();
- mProductId = device.getProductId();
- mClass = device.getDeviceClass();
- mSubclass = device.getDeviceSubclass();
- mProtocol = device.getDeviceProtocol();
- mManufacturerName = device.getManufacturerName();
- mProductName = device.getProductName();
- mSerialNumber = device.getSerialNumber();
- }
-
- public static DeviceFilter read(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- int vendorId = -1;
- int productId = -1;
- int deviceClass = -1;
- int deviceSubclass = -1;
- int deviceProtocol = -1;
- String manufacturerName = null;
- String productName = null;
- String serialNumber = null;
-
- int count = parser.getAttributeCount();
- for (int i = 0; i < count; i++) {
- String name = parser.getAttributeName(i);
- String value = parser.getAttributeValue(i);
- // Attribute values are ints or strings
- if ("manufacturer-name".equals(name)) {
- manufacturerName = value;
- } else if ("product-name".equals(name)) {
- productName = value;
- } else if ("serial-number".equals(name)) {
- serialNumber = value;
- } else {
- int intValue = -1;
- int radix = 10;
- if (value != null && value.length() > 2 && value.charAt(0) == '0' &&
- (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
- // allow hex values starting with 0x or 0X
- radix = 16;
- value = value.substring(2);
- }
- try {
- intValue = Integer.parseInt(value, radix);
- } catch (NumberFormatException e) {
- Slog.e(TAG, "invalid number for field " + name, e);
- continue;
- }
- if ("vendor-id".equals(name)) {
- vendorId = intValue;
- } else if ("product-id".equals(name)) {
- productId = intValue;
- } else if ("class".equals(name)) {
- deviceClass = intValue;
- } else if ("subclass".equals(name)) {
- deviceSubclass = intValue;
- } else if ("protocol".equals(name)) {
- deviceProtocol = intValue;
- }
- }
- }
- return new DeviceFilter(vendorId, productId,
- deviceClass, deviceSubclass, deviceProtocol,
- manufacturerName, productName, serialNumber);
- }
-
- public void write(XmlSerializer serializer) throws IOException {
- serializer.startTag(null, "usb-device");
- if (mVendorId != -1) {
- serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
- }
- if (mProductId != -1) {
- serializer.attribute(null, "product-id", Integer.toString(mProductId));
- }
- if (mClass != -1) {
- serializer.attribute(null, "class", Integer.toString(mClass));
- }
- if (mSubclass != -1) {
- serializer.attribute(null, "subclass", Integer.toString(mSubclass));
- }
- if (mProtocol != -1) {
- serializer.attribute(null, "protocol", Integer.toString(mProtocol));
- }
- if (mManufacturerName != null) {
- serializer.attribute(null, "manufacturer-name", mManufacturerName);
- }
- if (mProductName != null) {
- serializer.attribute(null, "product-name", mProductName);
- }
- if (mSerialNumber != null) {
- serializer.attribute(null, "serial-number", mSerialNumber);
- }
- serializer.endTag(null, "usb-device");
- }
-
- private boolean matches(int clasz, int subclass, int protocol) {
- return ((mClass == -1 || clasz == mClass) &&
- (mSubclass == -1 || subclass == mSubclass) &&
- (mProtocol == -1 || protocol == mProtocol));
- }
-
- public boolean matches(UsbDevice device) {
- if (mVendorId != -1 && device.getVendorId() != mVendorId) return false;
- if (mProductId != -1 && device.getProductId() != mProductId) return false;
- if (mManufacturerName != null && device.getManufacturerName() == null) return false;
- if (mProductName != null && device.getProductName() == null) return false;
- if (mSerialNumber != null && device.getSerialNumber() == null) return false;
- if (mManufacturerName != null && device.getManufacturerName() != null &&
- !mManufacturerName.equals(device.getManufacturerName())) return false;
- if (mProductName != null && device.getProductName() != null &&
- !mProductName.equals(device.getProductName())) return false;
- if (mSerialNumber != null && device.getSerialNumber() != null &&
- !mSerialNumber.equals(device.getSerialNumber())) return false;
-
- // check device class/subclass/protocol
- if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
- device.getDeviceProtocol())) return true;
-
- // if device doesn't match, check the interfaces
- int count = device.getInterfaceCount();
- for (int i = 0; i < count; i++) {
- UsbInterface intf = device.getInterface(i);
- if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
- intf.getInterfaceProtocol())) return true;
- }
-
- return false;
- }
-
- public boolean matches(DeviceFilter f) {
- if (mVendorId != -1 && f.mVendorId != mVendorId) return false;
- if (mProductId != -1 && f.mProductId != mProductId) return false;
- if (f.mManufacturerName != null && mManufacturerName == null) return false;
- if (f.mProductName != null && mProductName == null) return false;
- if (f.mSerialNumber != null && mSerialNumber == null) return false;
- if (mManufacturerName != null && f.mManufacturerName != null &&
- !mManufacturerName.equals(f.mManufacturerName)) return false;
- if (mProductName != null && f.mProductName != null &&
- !mProductName.equals(f.mProductName)) return false;
- if (mSerialNumber != null && f.mSerialNumber != null &&
- !mSerialNumber.equals(f.mSerialNumber)) return false;
-
- // check device class/subclass/protocol
- return matches(f.mClass, f.mSubclass, f.mProtocol);
- }
-
- @Override
- public boolean equals(Object obj) {
- // can't compare if we have wildcard strings
- if (mVendorId == -1 || mProductId == -1 ||
- mClass == -1 || mSubclass == -1 || mProtocol == -1) {
- return false;
- }
- if (obj instanceof DeviceFilter) {
- DeviceFilter filter = (DeviceFilter)obj;
-
- if (filter.mVendorId != mVendorId ||
- filter.mProductId != mProductId ||
- filter.mClass != mClass ||
- filter.mSubclass != mSubclass ||
- filter.mProtocol != mProtocol) {
- return(false);
- }
- if ((filter.mManufacturerName != null &&
- mManufacturerName == null) ||
- (filter.mManufacturerName == null &&
- mManufacturerName != null) ||
- (filter.mProductName != null &&
- mProductName == null) ||
- (filter.mProductName == null &&
- mProductName != null) ||
- (filter.mSerialNumber != null &&
- mSerialNumber == null) ||
- (filter.mSerialNumber == null &&
- mSerialNumber != null)) {
- return(false);
- }
- if ((filter.mManufacturerName != null &&
- mManufacturerName != null &&
- !mManufacturerName.equals(filter.mManufacturerName)) ||
- (filter.mProductName != null &&
- mProductName != null &&
- !mProductName.equals(filter.mProductName)) ||
- (filter.mSerialNumber != null &&
- mSerialNumber != null &&
- !mSerialNumber.equals(filter.mSerialNumber))) {
- return(false);
- }
- return(true);
- }
- if (obj instanceof UsbDevice) {
- UsbDevice device = (UsbDevice)obj;
- if (device.getVendorId() != mVendorId ||
- device.getProductId() != mProductId ||
- device.getDeviceClass() != mClass ||
- device.getDeviceSubclass() != mSubclass ||
- device.getDeviceProtocol() != mProtocol) {
- return(false);
- }
- if ((mManufacturerName != null && device.getManufacturerName() == null) ||
- (mManufacturerName == null && device.getManufacturerName() != null) ||
- (mProductName != null && device.getProductName() == null) ||
- (mProductName == null && device.getProductName() != null) ||
- (mSerialNumber != null && device.getSerialNumber() == null) ||
- (mSerialNumber == null && device.getSerialNumber() != null)) {
- return(false);
- }
- if ((device.getManufacturerName() != null &&
- !mManufacturerName.equals(device.getManufacturerName())) ||
- (device.getProductName() != null &&
- !mProductName.equals(device.getProductName())) ||
- (device.getSerialNumber() != null &&
- !mSerialNumber.equals(device.getSerialNumber()))) {
- return(false);
- }
- return true;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return (((mVendorId << 16) | mProductId) ^
- ((mClass << 16) | (mSubclass << 8) | mProtocol));
- }
-
- @Override
- public String toString() {
- return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
- ",mClass=" + mClass + ",mSubclass=" + mSubclass +
- ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
- ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
- "]";
- }
- }
-
- // This class is used to describe a USB accessory.
- // When used in HashMaps all values must be specified,
- // but wildcards can be used for any of the fields in
- // the package meta-data.
- private static class AccessoryFilter {
- // USB accessory manufacturer (or null for unspecified)
- public final String mManufacturer;
- // USB accessory model (or null for unspecified)
- public final String mModel;
- // USB accessory version (or null for unspecified)
- public final String mVersion;
-
- public AccessoryFilter(String manufacturer, String model, String version) {
- mManufacturer = manufacturer;
- mModel = model;
- mVersion = version;
- }
-
- public AccessoryFilter(UsbAccessory accessory) {
- mManufacturer = accessory.getManufacturer();
- mModel = accessory.getModel();
- mVersion = accessory.getVersion();
- }
-
- public static AccessoryFilter read(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- String manufacturer = null;
- String model = null;
- String version = null;
-
- int count = parser.getAttributeCount();
- for (int i = 0; i < count; i++) {
- String name = parser.getAttributeName(i);
- String value = parser.getAttributeValue(i);
-
- if ("manufacturer".equals(name)) {
- manufacturer = value;
- } else if ("model".equals(name)) {
- model = value;
- } else if ("version".equals(name)) {
- version = value;
- }
- }
- return new AccessoryFilter(manufacturer, model, version);
- }
-
- public void write(XmlSerializer serializer)throws IOException {
- serializer.startTag(null, "usb-accessory");
- if (mManufacturer != null) {
- serializer.attribute(null, "manufacturer", mManufacturer);
- }
- if (mModel != null) {
- serializer.attribute(null, "model", mModel);
- }
- if (mVersion != null) {
- serializer.attribute(null, "version", mVersion);
- }
- serializer.endTag(null, "usb-accessory");
- }
-
- public boolean matches(UsbAccessory acc) {
- if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
- if (mModel != null && !acc.getModel().equals(mModel)) return false;
- if (mVersion != null && !acc.getVersion().equals(mVersion)) return false;
- return true;
- }
-
- public boolean matches(AccessoryFilter f) {
- if (mManufacturer != null && !f.mManufacturer.equals(mManufacturer)) return false;
- if (mModel != null && !f.mModel.equals(mModel)) return false;
- if (mVersion != null && !f.mVersion.equals(mVersion)) return false;
- return true;
- }
-
- @Override
- public boolean equals(Object obj) {
- // can't compare if we have wildcard strings
- if (mManufacturer == null || mModel == null || mVersion == null) {
- return false;
- }
- if (obj instanceof AccessoryFilter) {
- AccessoryFilter filter = (AccessoryFilter)obj;
- return (mManufacturer.equals(filter.mManufacturer) &&
- mModel.equals(filter.mModel) &&
- mVersion.equals(filter.mVersion));
- }
- if (obj instanceof UsbAccessory) {
- UsbAccessory accessory = (UsbAccessory)obj;
- return (mManufacturer.equals(accessory.getManufacturer()) &&
- mModel.equals(accessory.getModel()) &&
- mVersion.equals(accessory.getVersion()));
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
- (mModel == null ? 0 : mModel.hashCode()) ^
- (mVersion == null ? 0 : mVersion.hashCode()));
- }
-
- @Override
- public String toString() {
- return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
- "\", mModel=\"" + mModel +
- "\", mVersion=\"" + mVersion + "\"]";
- }
- }
-
- private class MyPackageMonitor extends PackageMonitor {
- @Override
- public void onPackageAdded(String packageName, int uid) {
- handlePackageUpdate(packageName);
- }
-
- @Override
- public boolean onPackageChanged(String packageName, int uid, String[] components) {
- handlePackageUpdate(packageName);
- return false;
- }
-
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- clearDefaults(packageName);
- }
- }
-
- MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
-
- private final MtpNotificationManager mMtpNotificationManager;
-
- public UsbSettingsManager(Context context, UserHandle user) {
- if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
-
- try {
- mUserContext = context.createPackageContextAsUser("android", 0, user);
- } catch (NameNotFoundException e) {
- throw new RuntimeException("Missing android package");
- }
-
+ public UsbSettingsManager(@NonNull Context context) {
mContext = context;
- mPackageManager = mUserContext.getPackageManager();
-
- mUser = user;
- mSettingsFile = new AtomicFile(new File(
- Environment.getUserSystemDirectory(user.getIdentifier()),
- "usb_device_manager.xml"));
-
- mDisablePermissionDialogs = context.getResources().getBoolean(
- com.android.internal.R.bool.config_disableUsbPermissionDialogs);
-
- synchronized (mLock) {
- if (UserHandle.SYSTEM.equals(user)) {
- upgradeSingleUserLocked();
- }
- readSettingsLocked();
- }
-
- mPackageMonitor.register(mUserContext, null, true);
- mMtpNotificationManager = new MtpNotificationManager(
- context,
- new MtpNotificationManager.OnOpenInAppListener() {
- @Override
- public void onOpenInApp(UsbDevice device) {
- resolveActivity(createDeviceAttachedIntent(device), device);
- }
- });
- }
-
- private void readPreference(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- String packageName = null;
- int count = parser.getAttributeCount();
- for (int i = 0; i < count; i++) {
- if ("package".equals(parser.getAttributeName(i))) {
- packageName = parser.getAttributeValue(i);
- break;
- }
- }
- XmlUtils.nextElement(parser);
- if ("usb-device".equals(parser.getName())) {
- DeviceFilter filter = DeviceFilter.read(parser);
- mDevicePreferenceMap.put(filter, packageName);
- } else if ("usb-accessory".equals(parser.getName())) {
- AccessoryFilter filter = AccessoryFilter.read(parser);
- mAccessoryPreferenceMap.put(filter, packageName);
- }
- XmlUtils.nextElement(parser);
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
}
/**
- * Upgrade any single-user settings from {@link #sSingleUserSettingsFile}.
- * Should only by called by owner.
+ * Get the {@link UsbUserSettingsManager} for a user.
+ *
+ * @param userId The id of the user
+ *
+ * @return The settings for the user
*/
- private void upgradeSingleUserLocked() {
- if (sSingleUserSettingsFile.exists()) {
- mDevicePreferenceMap.clear();
- mAccessoryPreferenceMap.clear();
-
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(sSingleUserSettingsFile);
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(fis, StandardCharsets.UTF_8.name());
-
- XmlUtils.nextElement(parser);
- while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
- final String tagName = parser.getName();
- if ("preference".equals(tagName)) {
- readPreference(parser);
- } else {
- XmlUtils.nextElement(parser);
- }
- }
- } catch (IOException e) {
- Log.wtf(TAG, "Failed to read single-user settings", e);
- } catch (XmlPullParserException e) {
- Log.wtf(TAG, "Failed to read single-user settings", e);
- } finally {
- IoUtils.closeQuietly(fis);
+ @NonNull UsbUserSettingsManager getSettingsForUser(@UserIdInt int userId) {
+ synchronized (mSettingsByUser) {
+ UsbUserSettingsManager settings = mSettingsByUser.get(userId);
+ if (settings == null) {
+ settings = new UsbUserSettingsManager(mContext, new UserHandle(userId));
+ mSettingsByUser.put(userId, settings);
}
-
- writeSettingsLocked();
-
- // Success or failure, we delete single-user file
- sSingleUserSettingsFile.delete();
+ return settings;
}
}
- private void readSettingsLocked() {
- if (DEBUG) Slog.v(TAG, "readSettingsLocked()");
+ /**
+ * Get the {@link UsbProfileGroupSettingsManager} for a user.
+ *
+ * @param user Any user of the profile group
+ *
+ * @return The settings for the profile group
+ */
+ @NonNull UsbProfileGroupSettingsManager getSettingsForProfileGroup(@NonNull UserHandle user) {
+ UserHandle parentUser;
- mDevicePreferenceMap.clear();
- mAccessoryPreferenceMap.clear();
-
- FileInputStream stream = null;
- try {
- stream = mSettingsFile.openRead();
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(stream, StandardCharsets.UTF_8.name());
-
- XmlUtils.nextElement(parser);
- while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if ("preference".equals(tagName)) {
- readPreference(parser);
- } else {
- XmlUtils.nextElement(parser);
- }
- }
- } catch (FileNotFoundException e) {
- if (DEBUG) Slog.d(TAG, "settings file not found");
- } catch (Exception e) {
- Slog.e(TAG, "error reading settings file, deleting to start fresh", e);
- mSettingsFile.delete();
- } finally {
- IoUtils.closeQuietly(stream);
- }
- }
-
- private void writeSettingsLocked() {
- if (DEBUG) Slog.v(TAG, "writeSettingsLocked()");
-
- FileOutputStream fos = null;
- try {
- fos = mSettingsFile.startWrite();
-
- FastXmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(fos, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- serializer.startTag(null, "settings");
-
- for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
- serializer.startTag(null, "preference");
- serializer.attribute(null, "package", mDevicePreferenceMap.get(filter));
- filter.write(serializer);
- serializer.endTag(null, "preference");
- }
-
- for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
- serializer.startTag(null, "preference");
- serializer.attribute(null, "package", mAccessoryPreferenceMap.get(filter));
- filter.write(serializer);
- serializer.endTag(null, "preference");
- }
-
- serializer.endTag(null, "settings");
- serializer.endDocument();
-
- mSettingsFile.finishWrite(fos);
- } catch (IOException e) {
- Slog.e(TAG, "Failed to write settings", e);
- if (fos != null) {
- mSettingsFile.failWrite(fos);
- }
- }
- }
-
- // Checks to see if a package matches a device or accessory.
- // Only one of device and accessory should be non-null.
- private boolean packageMatchesLocked(ResolveInfo info, String metaDataName,
- UsbDevice device, UsbAccessory accessory) {
- ActivityInfo ai = info.activityInfo;
-
- XmlResourceParser parser = null;
- try {
- parser = ai.loadXmlMetaData(mPackageManager, metaDataName);
- if (parser == null) {
- Slog.w(TAG, "no meta-data for " + info);
- return false;
- }
-
- XmlUtils.nextElement(parser);
- while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (device != null && "usb-device".equals(tagName)) {
- DeviceFilter filter = DeviceFilter.read(parser);
- if (filter.matches(device)) {
- return true;
- }
- }
- else if (accessory != null && "usb-accessory".equals(tagName)) {
- AccessoryFilter filter = AccessoryFilter.read(parser);
- if (filter.matches(accessory)) {
- return true;
- }
- }
- XmlUtils.nextElement(parser);
- }
- } catch (Exception e) {
- Slog.w(TAG, "Unable to load component info " + info.toString(), e);
- } finally {
- if (parser != null) parser.close();
- }
- return false;
- }
-
- private final ArrayList<ResolveInfo> getDeviceMatchesLocked(UsbDevice device, Intent intent) {
- ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
- List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent,
- PackageManager.GET_META_DATA);
- int count = resolveInfos.size();
- for (int i = 0; i < count; i++) {
- ResolveInfo resolveInfo = resolveInfos.get(i);
- if (packageMatchesLocked(resolveInfo, intent.getAction(), device, null)) {
- matches.add(resolveInfo);
- }
- }
- return matches;
- }
-
- private final ArrayList<ResolveInfo> getAccessoryMatchesLocked(
- UsbAccessory accessory, Intent intent) {
- ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
- List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent,
- PackageManager.GET_META_DATA);
- int count = resolveInfos.size();
- for (int i = 0; i < count; i++) {
- ResolveInfo resolveInfo = resolveInfos.get(i);
- if (packageMatchesLocked(resolveInfo, intent.getAction(), null, accessory)) {
- matches.add(resolveInfo);
- }
- }
- return matches;
- }
-
- public void deviceAttached(UsbDevice device) {
- final Intent intent = createDeviceAttachedIntent(device);
-
- // Send broadcast to running activity with registered intent
- mUserContext.sendBroadcast(intent);
-
- if (MtpNotificationManager.shouldShowNotification(mPackageManager, device)) {
- // Show notification if the device is MTP storage.
- mMtpNotificationManager.showNotification(device);
+ UserInfo parentUserInfo = mUserManager.getProfileParent(user.getIdentifier());
+ if (parentUserInfo != null) {
+ parentUser = parentUserInfo.getUserHandle();
} else {
- resolveActivity(intent, device);
+ parentUser = user;
+ }
+
+ synchronized (mSettingsByProfileGroup) {
+ UsbProfileGroupSettingsManager settings = mSettingsByProfileGroup.get(
+ parentUser.getIdentifier());
+ if (settings == null) {
+ settings = new UsbProfileGroupSettingsManager(mContext, parentUser, this);
+ mSettingsByProfileGroup.put(parentUser.getIdentifier(), settings);
+ }
+ return settings;
}
}
- private void resolveActivity(Intent intent, UsbDevice device) {
- ArrayList<ResolveInfo> matches;
- String defaultPackage;
- synchronized (mLock) {
- matches = getDeviceMatchesLocked(device, intent);
- // Launch our default activity directly, if we have one.
- // Otherwise we will start the UsbResolverActivity to allow the user to choose.
- defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device));
- }
-
- // Start activity with registered intent
- resolveActivity(intent, matches, defaultPackage, device, null);
- }
-
- public void deviceAttachedForFixedHandler(UsbDevice device, ComponentName component) {
- final Intent intent = createDeviceAttachedIntent(device);
-
- // Send broadcast to running activity with registered intent
- mUserContext.sendBroadcast(intent);
-
- ApplicationInfo appInfo;
- try {
- appInfo = mPackageManager.getApplicationInfo(component.getPackageName(), 0);
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "Default USB handling package not found: " + component.getPackageName());
- return;
- }
-
- grantDevicePermission(device, appInfo.uid);
-
- Intent activityIntent = new Intent(intent);
- activityIntent.setComponent(component);
- try {
- mUserContext.startActivityAsUser(activityIntent, mUser);
- } catch (ActivityNotFoundException e) {
- Slog.e(TAG, "unable to start activity " + activityIntent);
+ /**
+ * Remove the settings for a user.
+ *
+ * @param userIdToRemove The user o remove
+ */
+ void remove(@UserIdInt int userIdToRemove) {
+ synchronized (mSettingsByUser) {
+ mSettingsByUser.remove(userIdToRemove);
}
}
- public void deviceDetached(UsbDevice device) {
- // clear temporary permissions for the device
- mDevicePermissionMap.remove(device.getDeviceName());
+ /**
+ * Dump all settings of all users.
+ *
+ * @param pw The writer to dump to
+ */
+ void dump(@NonNull IndentingPrintWriter pw) {
+ synchronized (mSettingsByUser) {
+ int numUsers = mSettingsByUser.size();
+ for (int i = 0; i < numUsers; i++) {
+ final int userId = mSettingsByUser.keyAt(i);
+ final UsbUserSettingsManager settings = mSettingsByUser.valueAt(i);
+ pw.println("Settings for user " + userId + ":");
+ pw.increaseIndent();
+ try {
+ settings.dump(pw);
+ } finally {
+ pw.decreaseIndent();
+ }
+ }
+ }
+
+ synchronized (mSettingsByProfileGroup) {
+ int numProfileGroups = mSettingsByProfileGroup.size();
+ for (int i = 0; i < numProfileGroups; i++) {
+ final int parentUserId = mSettingsByProfileGroup.keyAt(i);
+ final UsbProfileGroupSettingsManager settings = mSettingsByProfileGroup.valueAt(i);
+ pw.println("Settings for profile group " + parentUserId + ":");
+ pw.increaseIndent();
+ try {
+ settings.dump(pw);
+ } finally {
+ pw.decreaseIndent();
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove temporary access permission and broadcast that a device was removed.
+ *
+ * @param device The device that is removed
+ */
+ void usbDeviceRemoved(@NonNull UsbDevice device) {
+ synchronized (mSettingsByUser) {
+ for (int i = 0; i < mSettingsByUser.size(); i++) {
+ // clear temporary permissions for the device
+ mSettingsByUser.valueAt(i).removeDevicePermissions(device);
+ }
+ }
Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED);
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
- if (DEBUG) Slog.d(TAG, "usbDeviceRemoved, sending " + intent);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- mMtpNotificationManager.hideNotification(device.getDeviceId());
- }
-
- public void accessoryAttached(UsbAccessory accessory) {
- Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
- intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- ArrayList<ResolveInfo> matches;
- String defaultPackage;
- synchronized (mLock) {
- matches = getAccessoryMatchesLocked(accessory, intent);
- // Launch our default activity directly, if we have one.
- // Otherwise we will start the UsbResolverActivity to allow the user to choose.
- defaultPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory));
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "usbDeviceRemoved, sending " + intent);
}
-
- resolveActivity(intent, matches, defaultPackage, null, accessory);
- }
-
- public void accessoryDetached(UsbAccessory accessory) {
- // clear temporary permissions for the accessory
- mAccessoryPermissionMap.remove(accessory);
-
- Intent intent = new Intent(
- UsbManager.ACTION_USB_ACCESSORY_DETACHED);
- intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
- private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches,
- String defaultPackage, UsbDevice device, UsbAccessory accessory) {
- int count = matches.size();
-
- // don't show the resolver activity if there are no choices available
- if (count == 0) {
- if (accessory != null) {
- String uri = accessory.getUri();
- if (uri != null && uri.length() > 0) {
- // display URI to user
- // start UsbResolverActivity so user can choose an activity
- Intent dialogIntent = new Intent();
- dialogIntent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbAccessoryUriActivity");
- dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
- dialogIntent.putExtra("uri", uri);
- try {
- mUserContext.startActivityAsUser(dialogIntent, mUser);
- } catch (ActivityNotFoundException e) {
- Slog.e(TAG, "unable to start UsbAccessoryUriActivity");
- }
- }
- }
-
- // do nothing
- return;
- }
-
- ResolveInfo defaultRI = null;
- if (count == 1 && defaultPackage == null) {
- // Check to see if our single choice is on the system partition.
- // If so, treat it as our default without calling UsbResolverActivity
- ResolveInfo rInfo = matches.get(0);
- if (rInfo.activityInfo != null &&
- rInfo.activityInfo.applicationInfo != null &&
- (rInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- defaultRI = rInfo;
- }
-
- if (mDisablePermissionDialogs) {
- // bypass dialog and launch the only matching activity
- rInfo = matches.get(0);
- if (rInfo.activityInfo != null) {
- defaultPackage = rInfo.activityInfo.packageName;
- }
+ /**
+ * Remove temporary access permission and broadcast that a accessory was removed.
+ *
+ * @param accessory The accessory that is removed
+ */
+ void usbAccessoryRemoved(@NonNull UsbAccessory accessory) {
+ synchronized (mSettingsByUser) {
+ for (int i = 0; i < mSettingsByUser.size(); i++) {
+ // clear temporary permissions for the accessory
+ mSettingsByUser.valueAt(i).removeAccessoryPermissions(accessory);
}
}
- if (defaultRI == null && defaultPackage != null) {
- // look for default activity
- for (int i = 0; i < count; i++) {
- ResolveInfo rInfo = matches.get(i);
- if (rInfo.activityInfo != null &&
- defaultPackage.equals(rInfo.activityInfo.packageName)) {
- defaultRI = rInfo;
- break;
- }
- }
- }
-
- if (defaultRI != null) {
- // grant permission for default activity
- if (device != null) {
- grantDevicePermission(device, defaultRI.activityInfo.applicationInfo.uid);
- } else if (accessory != null) {
- grantAccessoryPermission(accessory, defaultRI.activityInfo.applicationInfo.uid);
- }
-
- // start default activity directly
- try {
- intent.setComponent(
- new ComponentName(defaultRI.activityInfo.packageName,
- defaultRI.activityInfo.name));
- mUserContext.startActivityAsUser(intent, mUser);
- } catch (ActivityNotFoundException e) {
- Slog.e(TAG, "startActivity failed", e);
- }
- } else {
- Intent resolverIntent = new Intent();
- resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- if (count == 1) {
- // start UsbConfirmActivity if there is only one choice
- resolverIntent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbConfirmActivity");
- resolverIntent.putExtra("rinfo", matches.get(0));
-
- if (device != null) {
- resolverIntent.putExtra(UsbManager.EXTRA_DEVICE, device);
- } else {
- resolverIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
- }
- } else {
- // start UsbResolverActivity so user can choose an activity
- resolverIntent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbResolverActivity");
- resolverIntent.putParcelableArrayListExtra("rlist", matches);
- resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
- }
- try {
- mUserContext.startActivityAsUser(resolverIntent, mUser);
- } catch (ActivityNotFoundException e) {
- Slog.e(TAG, "unable to start activity " + resolverIntent);
- }
- }
- }
-
- private boolean clearCompatibleMatchesLocked(String packageName, DeviceFilter filter) {
- boolean changed = false;
- for (DeviceFilter test : mDevicePreferenceMap.keySet()) {
- if (filter.matches(test)) {
- mDevicePreferenceMap.remove(test);
- changed = true;
- }
- }
- return changed;
- }
-
- private boolean clearCompatibleMatchesLocked(String packageName, AccessoryFilter filter) {
- boolean changed = false;
- for (AccessoryFilter test : mAccessoryPreferenceMap.keySet()) {
- if (filter.matches(test)) {
- mAccessoryPreferenceMap.remove(test);
- changed = true;
- }
- }
- return changed;
- }
-
- private boolean handlePackageUpdateLocked(String packageName, ActivityInfo aInfo,
- String metaDataName) {
- XmlResourceParser parser = null;
- boolean changed = false;
-
- try {
- parser = aInfo.loadXmlMetaData(mPackageManager, metaDataName);
- if (parser == null) return false;
-
- XmlUtils.nextElement(parser);
- while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if ("usb-device".equals(tagName)) {
- DeviceFilter filter = DeviceFilter.read(parser);
- if (clearCompatibleMatchesLocked(packageName, filter)) {
- changed = true;
- }
- }
- else if ("usb-accessory".equals(tagName)) {
- AccessoryFilter filter = AccessoryFilter.read(parser);
- if (clearCompatibleMatchesLocked(packageName, filter)) {
- changed = true;
- }
- }
- XmlUtils.nextElement(parser);
- }
- } catch (Exception e) {
- Slog.w(TAG, "Unable to load component info " + aInfo.toString(), e);
- } finally {
- if (parser != null) parser.close();
- }
- return changed;
- }
-
- // Check to see if the package supports any USB devices or accessories.
- // If so, clear any non-matching preferences for matching devices/accessories.
- private void handlePackageUpdate(String packageName) {
- synchronized (mLock) {
- PackageInfo info;
- boolean changed = false;
-
- try {
- info = mPackageManager.getPackageInfo(packageName,
- PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
- return;
- }
-
- ActivityInfo[] activities = info.activities;
- if (activities == null) return;
- for (int i = 0; i < activities.length; i++) {
- // check for meta-data, both for devices and accessories
- if (handlePackageUpdateLocked(packageName, activities[i],
- UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
- changed = true;
- }
- if (handlePackageUpdateLocked(packageName, activities[i],
- UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
- changed = true;
- }
- }
-
- if (changed) {
- writeSettingsLocked();
- }
- }
- }
-
- public boolean hasPermission(UsbDevice device) {
- synchronized (mLock) {
- int uid = Binder.getCallingUid();
- if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
- return true;
- }
- SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
- if (uidList == null) {
- return false;
- }
- return uidList.get(uid);
- }
- }
-
- public boolean hasPermission(UsbAccessory accessory) {
- synchronized (mLock) {
- int uid = Binder.getCallingUid();
- if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
- return true;
- }
- SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
- if (uidList == null) {
- return false;
- }
- return uidList.get(uid);
- }
- }
-
- public void checkPermission(UsbDevice device) {
- if (!hasPermission(device)) {
- throw new SecurityException("User has not given permission to device " + device);
- }
- }
-
- public void checkPermission(UsbAccessory accessory) {
- if (!hasPermission(accessory)) {
- throw new SecurityException("User has not given permission to accessory " + accessory);
- }
- }
-
- private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) {
- final int uid = Binder.getCallingUid();
-
- // compare uid with packageName to foil apps pretending to be someone else
- try {
- ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
- if (aInfo.uid != uid) {
- throw new IllegalArgumentException("package " + packageName +
- " does not match caller's uid " + uid);
- }
- } catch (PackageManager.NameNotFoundException e) {
- throw new IllegalArgumentException("package " + packageName + " not found");
- }
-
- long identity = Binder.clearCallingIdentity();
- intent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbPermissionActivity");
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(Intent.EXTRA_INTENT, pi);
- intent.putExtra("package", packageName);
- intent.putExtra(Intent.EXTRA_UID, uid);
- try {
- mUserContext.startActivityAsUser(intent, mUser);
- } catch (ActivityNotFoundException e) {
- Slog.e(TAG, "unable to start UsbPermissionActivity");
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
- Intent intent = new Intent();
-
- // respond immediately if permission has already been granted
- if (hasPermission(device)) {
- intent.putExtra(UsbManager.EXTRA_DEVICE, device);
- intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
- try {
- pi.send(mUserContext, 0, intent);
- } catch (PendingIntent.CanceledException e) {
- if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
- }
- return;
- }
-
- // start UsbPermissionActivity so user can choose an activity
- intent.putExtra(UsbManager.EXTRA_DEVICE, device);
- requestPermissionDialog(intent, packageName, pi);
- }
-
- public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) {
- Intent intent = new Intent();
-
- // respond immediately if permission has already been granted
- if (hasPermission(accessory)) {
- intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
- intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
- try {
- pi.send(mUserContext, 0, intent);
- } catch (PendingIntent.CanceledException e) {
- if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
- }
- return;
- }
-
+ Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
- requestPermissionDialog(intent, packageName, pi);
- }
-
- public void setDevicePackage(UsbDevice device, String packageName) {
- DeviceFilter filter = new DeviceFilter(device);
- boolean changed = false;
- synchronized (mLock) {
- if (packageName == null) {
- changed = (mDevicePreferenceMap.remove(filter) != null);
- } else {
- changed = !packageName.equals(mDevicePreferenceMap.get(filter));
- if (changed) {
- mDevicePreferenceMap.put(filter, packageName);
- }
- }
- if (changed) {
- writeSettingsLocked();
- }
- }
- }
-
- public void setAccessoryPackage(UsbAccessory accessory, String packageName) {
- AccessoryFilter filter = new AccessoryFilter(accessory);
- boolean changed = false;
- synchronized (mLock) {
- if (packageName == null) {
- changed = (mAccessoryPreferenceMap.remove(filter) != null);
- } else {
- changed = !packageName.equals(mAccessoryPreferenceMap.get(filter));
- if (changed) {
- mAccessoryPreferenceMap.put(filter, packageName);
- }
- }
- if (changed) {
- writeSettingsLocked();
- }
- }
- }
-
- public void grantDevicePermission(UsbDevice device, int uid) {
- synchronized (mLock) {
- String deviceName = device.getDeviceName();
- SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
- if (uidList == null) {
- uidList = new SparseBooleanArray(1);
- mDevicePermissionMap.put(deviceName, uidList);
- }
- uidList.put(uid, true);
- }
- }
-
- public void grantAccessoryPermission(UsbAccessory accessory, int uid) {
- synchronized (mLock) {
- SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
- if (uidList == null) {
- uidList = new SparseBooleanArray(1);
- mAccessoryPermissionMap.put(accessory, uidList);
- }
- uidList.put(uid, true);
- }
- }
-
- public boolean hasDefaults(String packageName) {
- synchronized (mLock) {
- if (mDevicePreferenceMap.values().contains(packageName)) return true;
- if (mAccessoryPreferenceMap.values().contains(packageName)) return true;
- return false;
- }
- }
-
- public void clearDefaults(String packageName) {
- synchronized (mLock) {
- if (clearPackageDefaultsLocked(packageName)) {
- writeSettingsLocked();
- }
- }
- }
-
- private boolean clearPackageDefaultsLocked(String packageName) {
- boolean cleared = false;
- synchronized (mLock) {
- if (mDevicePreferenceMap.containsValue(packageName)) {
- // make a copy of the key set to avoid ConcurrentModificationException
- Object[] keys = mDevicePreferenceMap.keySet().toArray();
- for (int i = 0; i < keys.length; i++) {
- Object key = keys[i];
- if (packageName.equals(mDevicePreferenceMap.get(key))) {
- mDevicePreferenceMap.remove(key);
- cleared = true;
- }
- }
- }
- if (mAccessoryPreferenceMap.containsValue(packageName)) {
- // make a copy of the key set to avoid ConcurrentModificationException
- Object[] keys = mAccessoryPreferenceMap.keySet().toArray();
- for (int i = 0; i < keys.length; i++) {
- Object key = keys[i];
- if (packageName.equals(mAccessoryPreferenceMap.get(key))) {
- mAccessoryPreferenceMap.remove(key);
- cleared = true;
- }
- }
- }
- return cleared;
- }
- }
-
- public void dump(IndentingPrintWriter pw) {
- synchronized (mLock) {
- pw.println("Device permissions:");
- for (String deviceName : mDevicePermissionMap.keySet()) {
- pw.print(" " + deviceName + ": ");
- SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
- int count = uidList.size();
- for (int i = 0; i < count; i++) {
- pw.print(Integer.toString(uidList.keyAt(i)) + " ");
- }
- pw.println();
- }
- pw.println("Accessory permissions:");
- for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) {
- pw.print(" " + accessory + ": ");
- SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
- int count = uidList.size();
- for (int i = 0; i < count; i++) {
- pw.print(Integer.toString(uidList.keyAt(i)) + " ");
- }
- pw.println();
- }
- pw.println("Device preferences:");
- for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
- pw.println(" " + filter + ": " + mDevicePreferenceMap.get(filter));
- }
- pw.println("Accessory preferences:");
- for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
- pw.println(" " + filter + ": " + mAccessoryPreferenceMap.get(filter));
- }
- }
- }
-
- private static Intent createDeviceAttachedIntent(UsbDevice device) {
- Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
- intent.putExtra(UsbManager.EXTRA_DEVICE, device);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
new file mode 100644
index 0000000..4a34ece
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2011 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.usb;
+
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.os.Binder;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.HashMap;
+
+class UsbUserSettingsManager {
+ private static final String TAG = "UsbUserSettingsManager";
+ private static final boolean DEBUG = false;
+
+ private final UserHandle mUser;
+ private final boolean mDisablePermissionDialogs;
+
+ private final Context mUserContext;
+ private final PackageManager mPackageManager;
+
+ // Temporary mapping USB device name to list of UIDs with permissions for the device
+ private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
+ new HashMap<String, SparseBooleanArray>();
+ // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory
+ private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
+ new HashMap<UsbAccessory, SparseBooleanArray>();
+
+ private final Object mLock = new Object();
+
+ public UsbUserSettingsManager(Context context, UserHandle user) {
+ if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
+
+ try {
+ mUserContext = context.createPackageContextAsUser("android", 0, user);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException("Missing android package");
+ }
+
+ mPackageManager = mUserContext.getPackageManager();
+
+ mUser = user;
+
+ mDisablePermissionDialogs = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_disableUsbPermissionDialogs);
+ }
+
+ /**
+ * Remove all access permission for a device.
+ *
+ * @param device The device the permissions are for
+ */
+ void removeDevicePermissions(@NonNull UsbDevice device) {
+ synchronized (mLock) {
+ mDevicePermissionMap.remove(device.getDeviceName());
+ }
+ }
+
+ /**
+ * Remove all access permission for a accessory.
+ *
+ * @param accessory The accessory the permissions are for
+ */
+ void removeAccessoryPermissions(@NonNull UsbAccessory accessory) {
+ synchronized (mLock) {
+ mAccessoryPermissionMap.remove(accessory);
+ }
+ }
+
+
+ public boolean hasPermission(UsbDevice device) {
+ synchronized (mLock) {
+ int uid = Binder.getCallingUid();
+ if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
+ return true;
+ }
+ SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
+ if (uidList == null) {
+ return false;
+ }
+ return uidList.get(uid);
+ }
+ }
+
+ public boolean hasPermission(UsbAccessory accessory) {
+ synchronized (mLock) {
+ int uid = Binder.getCallingUid();
+ if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
+ return true;
+ }
+ SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
+ if (uidList == null) {
+ return false;
+ }
+ return uidList.get(uid);
+ }
+ }
+
+ public void checkPermission(UsbDevice device) {
+ if (!hasPermission(device)) {
+ throw new SecurityException("User has not given permission to device " + device);
+ }
+ }
+
+ public void checkPermission(UsbAccessory accessory) {
+ if (!hasPermission(accessory)) {
+ throw new SecurityException("User has not given permission to accessory " + accessory);
+ }
+ }
+
+ private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) {
+ final int uid = Binder.getCallingUid();
+
+ // compare uid with packageName to foil apps pretending to be someone else
+ try {
+ ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
+ if (aInfo.uid != uid) {
+ throw new IllegalArgumentException("package " + packageName +
+ " does not match caller's uid " + uid);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException("package " + packageName + " not found");
+ }
+
+ long identity = Binder.clearCallingIdentity();
+ intent.setClassName("com.android.systemui",
+ "com.android.systemui.usb.UsbPermissionActivity");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(Intent.EXTRA_INTENT, pi);
+ intent.putExtra("package", packageName);
+ intent.putExtra(Intent.EXTRA_UID, uid);
+ try {
+ mUserContext.startActivityAsUser(intent, mUser);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "unable to start UsbPermissionActivity");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
+ Intent intent = new Intent();
+
+ // respond immediately if permission has already been granted
+ if (hasPermission(device)) {
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
+ try {
+ pi.send(mUserContext, 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
+ }
+ return;
+ }
+
+ // start UsbPermissionActivity so user can choose an activity
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ requestPermissionDialog(intent, packageName, pi);
+ }
+
+ public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) {
+ Intent intent = new Intent();
+
+ // respond immediately if permission has already been granted
+ if (hasPermission(accessory)) {
+ intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+ intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
+ try {
+ pi.send(mUserContext, 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
+ }
+ return;
+ }
+
+ intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+ requestPermissionDialog(intent, packageName, pi);
+ }
+
+ public void grantDevicePermission(UsbDevice device, int uid) {
+ synchronized (mLock) {
+ String deviceName = device.getDeviceName();
+ SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
+ if (uidList == null) {
+ uidList = new SparseBooleanArray(1);
+ mDevicePermissionMap.put(deviceName, uidList);
+ }
+ uidList.put(uid, true);
+ }
+ }
+
+ public void grantAccessoryPermission(UsbAccessory accessory, int uid) {
+ synchronized (mLock) {
+ SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
+ if (uidList == null) {
+ uidList = new SparseBooleanArray(1);
+ mAccessoryPermissionMap.put(accessory, uidList);
+ }
+ uidList.put(uid, true);
+ }
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ pw.println("Device permissions:");
+ for (String deviceName : mDevicePermissionMap.keySet()) {
+ pw.print(" " + deviceName + ": ");
+ SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
+ int count = uidList.size();
+ for (int i = 0; i < count; i++) {
+ pw.print(Integer.toString(uidList.keyAt(i)) + " ");
+ }
+ pw.println();
+ }
+ pw.println("Accessory permissions:");
+ for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) {
+ pw.print(" " + accessory + ": ");
+ SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
+ int count = uidList.size();
+ for (int i = 0; i < count; i++) {
+ pw.print(Integer.toString(uidList.keyAt(i)) + " ");
+ }
+ pw.println();
+ }
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index e27ab52..e2b6b54 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -87,6 +87,14 @@
KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
/**
+ * Flag indicating whether radio is to be restarted on error PDP_FAIL_REGULAR_DEACTIVATION
+ * This is false by default.
+ */
+ public static final String
+ KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL =
+ "restart_radio_on_pdp_fail_regular_deactivation_bool";
+
+ /**
* If true, enable vibration (haptic feedback) for key presses in the EmergencyDialer activity.
* The pattern is set on a per-platform basis using config_virtualKeyVibePattern. To be
* consistent with the regular Dialer, this value should agree with the corresponding values
@@ -269,6 +277,14 @@
"support_downgrade_vt_to_audio_bool";
/**
+ * Where there is no preloaded voicemail number on a SIM card, specifies the carrier's default
+ * voicemail number.
+ * When empty string, no default voicemail number is specified.
+ */
+ public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
+
+
+ /**
* Flag specifying whether WFC over IMS should be available for carrier: independent of
* carrier provisioning. If false: hard disabled. If true: then depends on carrier
* provisioning, availability etc.
@@ -276,6 +292,16 @@
public static final String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
/**
+ * Specifies a map from dialstrings to replacements for roaming network service numbers which
+ * cannot be replaced on the carrier side.
+ * <p>
+ * Individual entries have the format:
+ * [dialstring to replace]:[replacement]
+ */
+ public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY =
+ "dial_string_replace_string_array";
+
+ /**
* Flag specifying whether WFC over IMS supports the "wifi only" option. If false, the wifi
* calling settings will not include an option for "wifi only". If true, the wifi calling
* settings will include an option for "wifi only"
@@ -433,18 +459,11 @@
"disable_severe_when_extreme_disabled_bool";
/**
- * The data call APN retry configuration for default type APN.
+ * The data call retry configuration for different types of APN.
* @hide
*/
- public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_DEFAULT_STRING =
- "carrier_data_call_retry_config_default_string";
-
- /**
- * The data call APN retry configuration for other type APNs.
- * @hide
- */
- public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_OTHERS_STRING =
- "carrier_data_call_retry_config_others_string";
+ public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS =
+ "carrier_data_call_retry_config_strings";
/**
* Delay between trying APN from the pool
@@ -577,6 +596,15 @@
public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
/**
+ * Determines whether High Definition audio property is displayed in the dialer UI.
+ * If {@code false}, remove the HD audio property from the connection so that HD audio related
+ * UI is not displayed. If {@code true}, keep HD audio property as it is configured.
+ * @hide
+ */
+ public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL =
+ "display_hd_audio_property_bool";
+
+ /**
* Determines whether video conference calls are supported by a carrier. When {@code true},
* video calls can be merged into conference calls, {@code false} otherwiwse.
* <p>
@@ -809,6 +837,14 @@
"duration_blocking_disabled_after_emergency_int";
/**
+ * For carriers which require an empty flash to be sent before sending the normal 3-way calling
+ * flash, the duration in milliseconds of the empty flash to send. When {@code 0}, no empty
+ * flash is sent.
+ */
+ public static final String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
+
+
+ /**
* @hide
* The default value for preferred CDMA roaming mode (aka CDMA system select.)
* CDMA_ROAMING_MODE_RADIO_DEFAULT = the default roaming mode from the radio
@@ -977,6 +1013,7 @@
sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true);
+ sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, "");
sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL, false);
@@ -1015,6 +1052,7 @@
sDefaults.putBoolean(KEY_VOICE_PRIVACY_DISABLE_UI_BOOL, false);
sDefaults.putBoolean(KEY_WORLD_PHONE_BOOL, false);
sDefaults.putBoolean(KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
+ sDefaults.putBoolean(KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL, false);
sDefaults.putInt(KEY_VOLTE_REPLACEMENT_RAT_INT, 0);
sDefaults.putString(KEY_DEFAULT_SIM_CALL_MANAGER_STRING, "");
sDefaults.putString(KEY_VVM_DESTINATION_NUMBER_STRING, "");
@@ -1033,11 +1071,12 @@
sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_SEVERE_WHEN_EXTREME_DISABLED_BOOL, true);
- sDefaults.putString(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_DEFAULT_STRING,
- "default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
- + "320000:5000,640000:5000,1280000:5000,1800000:5000");
- sDefaults.putString(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_OTHERS_STRING,
- "max_retries=3, 5000, 5000, 5000");
+ sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
+ "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+ + "320000:5000,640000:5000,1280000:5000,1800000:5000",
+ "mms:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+ + "320000:5000,640000:5000,1280000:5000,1800000:5000",
+ "others:max_retries=3, 5000, 5000, 5000"});
sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000);
sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000);
sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml");
@@ -1051,12 +1090,15 @@
sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null);
+ sDefaults.putStringArray(KEY_DIAL_STRING_REPLACE_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_FORCE_HOME_NETWORK_BOOL, false);
sDefaults.putInt(KEY_GSM_DTMF_TONE_DELAY_INT, 0);
sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0);
sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
+ sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
+ sDefaults.putBoolean(KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL, true);
sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 6b2ae3e..152b868 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.database.Cursor;
import android.location.CountryDetector;
import android.net.Uri;
@@ -3021,4 +3022,79 @@
return SubscriptionManager.getDefaultVoiceSubscriptionId();
}
//==== End of utility methods used only in compareStrictly() =====
+
+
+ /*
+ * The config held calling number conversion map, expected to convert to emergency number.
+ */
+ private static final String[] CONVERT_TO_EMERGENCY_MAP = Resources.getSystem().getStringArray(
+ com.android.internal.R.array.config_convert_to_emergency_number_map);
+ /**
+ * Check whether conversion to emergency number is enabled
+ *
+ * @return {@code true} when conversion to emergency numbers is enabled,
+ * {@code false} otherwise
+ *
+ * @hide
+ */
+ public static boolean isConvertToEmergencyNumberEnabled() {
+ return CONVERT_TO_EMERGENCY_MAP != null && CONVERT_TO_EMERGENCY_MAP.length > 0;
+ }
+
+ /**
+ * Converts to emergency number based on the conversion map.
+ * The conversion map is declared as config_convert_to_emergency_number_map.
+ *
+ * Make sure {@link #isConvertToEmergencyNumberEnabled} is true before calling
+ * this function.
+ *
+ * @return The converted emergency number if the number matches conversion map,
+ * otherwise original number.
+ *
+ * @hide
+ */
+ public static String convertToEmergencyNumber(String number) {
+ if (TextUtils.isEmpty(number)) {
+ return number;
+ }
+
+ String normalizedNumber = normalizeNumber(number);
+
+ // The number is already emergency number. Skip conversion.
+ if (isEmergencyNumber(normalizedNumber)) {
+ return number;
+ }
+
+ for (String convertMap : CONVERT_TO_EMERGENCY_MAP) {
+ if (DBG) log("convertToEmergencyNumber: " + convertMap);
+ String[] entry = null;
+ String[] filterNumbers = null;
+ String convertedNumber = null;
+ if (!TextUtils.isEmpty(convertMap)) {
+ entry = convertMap.split(":");
+ }
+ if (entry != null && entry.length == 2) {
+ convertedNumber = entry[1];
+ if (!TextUtils.isEmpty(entry[0])) {
+ filterNumbers = entry[0].split(",");
+ }
+ }
+ // Skip if the format of entry is invalid
+ if (TextUtils.isEmpty(convertedNumber) || filterNumbers == null
+ || filterNumbers.length == 0) {
+ continue;
+ }
+
+ for (String filterNumber : filterNumbers) {
+ if (DBG) log("convertToEmergencyNumber: filterNumber = " + filterNumber
+ + ", convertedNumber = " + convertedNumber);
+ if (!TextUtils.isEmpty(filterNumber) && filterNumber.equals(normalizedNumber)) {
+ if (DBG) log("convertToEmergencyNumber: Matched. Successfully converted to: "
+ + convertedNumber);
+ return convertedNumber;
+ }
+ }
+ }
+ return number;
+ }
}
diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
index 0111bc6..2ad0da9 100644
--- a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
+++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
@@ -15,6 +15,17 @@
Decoder d = new Decoder(ByteBuffer.wrap(data));
mViews = new ArrayList<>(100);
+
+ boolean dataIncludesWindowPosition = (data[0] == 'S');
+ Short windowLeftKey = null, windowTopKey = null;
+ Integer windowLeftValue = null, windowTopValue = null;
+ if (dataIncludesWindowPosition) {
+ windowLeftKey = (Short) d.readObject();
+ windowLeftValue = (Integer) d.readObject();
+ windowTopKey = (Short) d.readObject();
+ windowTopValue = (Integer) d.readObject();
+ }
+
while (d.hasRemaining()) {
Object o = d.readObject();
if (o instanceof Map) {
@@ -27,6 +38,11 @@
return;
}
+ if (dataIncludesWindowPosition) {
+ mViews.get(0).put(windowLeftKey, windowLeftValue);
+ mViews.get(0).put(windowTopKey, windowTopValue);
+ }
+
// the last one is the property map
Map<Short,Object> idMap = mViews.remove(mViews.size() - 1);
mIds = reverse(idMap);
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index 12143b7..cb5f6c7 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -93,13 +93,21 @@
</activity>
<activity
android:name=".TrivialRecyclerViewActivity"
- android:label="General/Trivial Recycler ListView" >
+ android:label="General/Trivial RecyclerView" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.test.uibench.TEST" />
</intent-filter>
</activity>
<activity
+ android:name=".SlowBindRecyclerViewActivity"
+ android:label="General/Slow Bind RecyclerView" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.uibench.TEST" />
+ </intent-filter>
+ </activity>
+ <activity
android:name=".ActivityTransition"
android:label="Transitions/Activity Transition" >
<intent-filter>
diff --git a/tests/UiBench/src/com/android/test/uibench/SlowBindRecyclerViewActivity.java b/tests/UiBench/src/com/android/test/uibench/SlowBindRecyclerViewActivity.java
new file mode 100644
index 0000000..e32862f
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/SlowBindRecyclerViewActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.uibench;
+
+import android.content.Context;
+import android.os.Trace;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import com.android.test.uibench.recyclerview.RvBoxAdapter;
+import com.android.test.uibench.recyclerview.RvCompatListActivity;
+
+import java.util.concurrent.TimeUnit;
+
+public class SlowBindRecyclerViewActivity extends RvCompatListActivity {
+ /**
+ * Spin wait. Used instead of sleeping so a core is used up for the duration, and so
+ * traces/sampled profiling show the sections as expensive, and not just a scheduling mistake.
+ */
+ private static void spinWaitMs(long ms) {
+ long start = System.nanoTime();
+ while (System.nanoTime() - start < TimeUnit.MILLISECONDS.toNanos(ms));
+ }
+
+ @Override
+ protected RecyclerView.LayoutManager createLayoutManager(Context context) {
+ return new GridLayoutManager(context, 3);
+ }
+
+ @Override
+ protected RecyclerView.Adapter createAdapter() {
+ return new RvBoxAdapter(this, TextUtils.buildSimpleStringList()) {
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ Trace.beginSection("bind item " + position);
+
+ spinWaitMs(3);
+ super.onBindViewHolder(holder, position);
+ Trace.endSection();
+ }
+ };
+ }
+}
diff --git a/tests/UiBench/src/com/android/test/uibench/recyclerview/RvBoxAdapter.java b/tests/UiBench/src/com/android/test/uibench/recyclerview/RvBoxAdapter.java
new file mode 100644
index 0000000..3440f19
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/recyclerview/RvBoxAdapter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.uibench.recyclerview;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.support.v7.widget.RecyclerView;
+import android.util.TypedValue;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class RvBoxAdapter extends RecyclerView.Adapter<RvBoxAdapter.ViewHolder> {
+
+ private int mBackground;
+
+ private List<String> mValues;
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public TextView mTextView;
+
+ public ViewHolder(TextView v) {
+ super(v);
+ mTextView = v;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " '" + mTextView.getText();
+ }
+ }
+
+ public RvBoxAdapter(Context context, String[] strings) {
+ TypedValue val = new TypedValue();
+ if (context.getTheme() != null) {
+ context.getTheme().resolveAttribute(
+ android.R.attr.selectableItemBackground, val, true);
+ }
+ mBackground = val.resourceId;
+ mValues = new ArrayList<>();
+ Collections.addAll(mValues, strings);
+ }
+
+ @Override
+ public RvBoxAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ final ViewHolder h = new ViewHolder(new TextView(parent.getContext()));
+ h.mTextView.setMinimumHeight(128);
+ h.mTextView.setPadding(20, 0, 20, 0);
+ h.mTextView.setFocusable(true);
+ h.mTextView.setBackgroundResource(mBackground);
+ RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ lp.leftMargin = 10;
+ lp.rightMargin = 5;
+ lp.topMargin = 20;
+ lp.bottomMargin = 15;
+ h.mTextView.setLayoutParams(lp);
+ return h;
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ holder.mTextView.setText(position + ":" + mValues.get(position));
+ holder.mTextView.setMinHeight((200 + mValues.get(position).length() * 10));
+ holder.mTextView.setBackgroundColor(getBackgroundColor(position));
+ }
+
+ private int getBackgroundColor(int position) {
+ switch (position % 4) {
+ case 0: return Color.LTGRAY;
+ case 1: return Color.RED;
+ case 2: return Color.DKGRAY;
+ case 3: return Color.BLUE;
+ }
+ return Color.TRANSPARENT;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mValues.size();
+ }
+}
diff --git a/tests/UiBench/src/com/android/test/uibench/recyclerview/RvCompatListActivity.java b/tests/UiBench/src/com/android/test/uibench/recyclerview/RvCompatListActivity.java
index e08dbc6..939b661 100644
--- a/tests/UiBench/src/com/android/test/uibench/recyclerview/RvCompatListActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/recyclerview/RvCompatListActivity.java
@@ -26,7 +26,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
import com.android.test.uibench.R;
diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py
index 372cb20..219fa2d 100755
--- a/tools/fonts/fontchain_lint.py
+++ b/tools/fonts/fontchain_lint.py
@@ -314,8 +314,11 @@
continue
# For later fonts, we only check them if they have a script
# defined, since the defined script may get them to a higher
- # score even if they appear after the emoji font.
- if emoji_font_seen and not record.scripts:
+ # score even if they appear after the emoji font. However,
+ # we should skip checking the text symbols font, since
+ # symbol fonts should be able to override the emoji display
+ # style when 'Zsym' is explicitly specified by the user.
+ if emoji_font_seen and (not record.scripts or 'Zsym' in record.scripts):
continue
# Check default emoji-style characters
diff --git a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
index 94f3f54..85584d3 100644
--- a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
+++ b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
@@ -34,7 +34,7 @@
Display display = wm.getDefaultDisplay();
ViewRootImpl root = new ViewRootImpl(context, display);
AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(),
- display, root, new Handler(), null);
+ display, root, new Handler(), null, context);
info.mHasWindowFocus = true;
info.mWindowVisibility = View.VISIBLE;
info.mInTouchMode = false; // this is so that we can display selections.
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 8b165bd..6ca13d6 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -96,7 +96,7 @@
}
@Override
- public void clearForcedDisplayDensity(int displayId) throws RemoteException {
+ public void clearForcedDisplayDensityForUser(int displayId, int userId) throws RemoteException {
// TODO Auto-generated method stub
}
@@ -397,7 +397,8 @@
}
@Override
- public void setForcedDisplayDensity(int displayId, int density) throws RemoteException {
+ public void setForcedDisplayDensityForUser(int displayId, int density, int userId)
+ throws RemoteException {
// TODO Auto-generated method stub
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
index a39eb4d..769ee33 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -620,7 +620,8 @@
int y = 0;
int width;
int height;
- Rectangle clipBounds = originalGraphics.getClipBounds();
+ Rectangle clipBounds = originalGraphics.getClip() != null ? originalGraphics
+ .getClipBounds() : null;
if (clipBounds != null) {
if (clipBounds.width == 0 || clipBounds.height == 0) {
// Clip is 0 so no need to paint anything.
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 716f1d3..06e1b68 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -167,18 +167,32 @@
* scan configuration parameters to be sent to {@link #startBackgroundScan}
*/
public static class ScanSettings implements Parcelable {
+ /**
+ * Hidden network to be scanned for.
+ * {@hide}
+ */
+ public static class HiddenNetwork {
+ /** SSID of the network */
+ public String ssid;
+
+ /**
+ * Default constructor for HiddenNetwork.
+ */
+ public HiddenNetwork(String ssid) {
+ this.ssid = ssid;
+ }
+ }
/** one of the WIFI_BAND values */
public int band;
/** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
public ChannelSpec[] channels;
/**
- * list of networkId's of hidden networks to scan for.
- * These Id's should correspond to the wpa_supplicant's networkId's and will be used
- * in connectivity scans using wpa_supplicant.
+ * list of hidden networks to scan for. Explicit probe requests are sent out for such
+ * networks during scan. Only valid for single scan requests.
* {@hide}
* */
- public int[] hiddenNetworkIds;
+ public HiddenNetwork[] hiddenNetworks;
/** period of background scan; in millisecond, 0 => single shot scan */
public int periodInMs;
/** must have a valid REPORT_EVENT value */
@@ -233,7 +247,14 @@
} else {
dest.writeInt(0);
}
- dest.writeIntArray(hiddenNetworkIds);
+ if (hiddenNetworks != null) {
+ dest.writeInt(hiddenNetworks.length);
+ for (int i = 0; i < hiddenNetworks.length; i++) {
+ dest.writeString(hiddenNetworks[i].ssid);
+ }
+ } else {
+ dest.writeInt(0);
+ }
}
/** Implement the Parcelable interface {@hide} */
@@ -258,7 +279,12 @@
spec.passive = in.readInt() == 1;
settings.channels[i] = spec;
}
- settings.hiddenNetworkIds = in.createIntArray();
+ int numNetworks = in.readInt();
+ settings.hiddenNetworks = new HiddenNetwork[numNetworks];
+ for (int i = 0; i < numNetworks; i++) {
+ String ssid = in.readString();
+ settings.hiddenNetworks[i] = new HiddenNetwork(ssid);;
+ }
return settings;
}
@@ -286,6 +312,12 @@
* {@hide}
*/
private int mBucketsScanned;
+ /**
+ * Indicates that the scan results received are as a result of a scan of all available
+ * channels. This should only be expected to function for single scans.
+ * {@hide}
+ */
+ private boolean mAllChannelsScanned;
/** all scan results discovered in this scan, sorted by timestamp in ascending order */
private ScanResult mResults[];
@@ -298,10 +330,12 @@
}
/** {@hide} */
- public ScanData(int id, int flags, int bucketsScanned, ScanResult[] results) {
+ public ScanData(int id, int flags, int bucketsScanned, boolean allChannelsScanned,
+ ScanResult[] results) {
mId = id;
mFlags = flags;
mBucketsScanned = bucketsScanned;
+ mAllChannelsScanned = allChannelsScanned;
mResults = results;
}
@@ -309,6 +343,7 @@
mId = s.mId;
mFlags = s.mFlags;
mBucketsScanned = s.mBucketsScanned;
+ mAllChannelsScanned = s.mAllChannelsScanned;
mResults = new ScanResult[s.mResults.length];
for (int i = 0; i < s.mResults.length; i++) {
ScanResult result = s.mResults[i];
@@ -330,6 +365,11 @@
return mBucketsScanned;
}
+ /** {@hide} */
+ public boolean isAllChannelsScanned() {
+ return mAllChannelsScanned;
+ }
+
public ScanResult[] getResults() {
return mResults;
}
@@ -345,6 +385,7 @@
dest.writeInt(mId);
dest.writeInt(mFlags);
dest.writeInt(mBucketsScanned);
+ dest.writeInt(mAllChannelsScanned ? 1 : 0);
dest.writeInt(mResults.length);
for (int i = 0; i < mResults.length; i++) {
ScanResult result = mResults[i];
@@ -362,12 +403,13 @@
int id = in.readInt();
int flags = in.readInt();
int bucketsScanned = in.readInt();
+ boolean allChannelsScanned = in.readInt() != 0;
int n = in.readInt();
ScanResult results[] = new ScanResult[n];
for (int i = 0; i < n; i++) {
results[i] = ScanResult.CREATOR.createFromParcel(in);
}
- return new ScanData(id, flags, bucketsScanned, results);
+ return new ScanData(id, flags, bucketsScanned, allChannelsScanned, results);
}
public ScanData[] newArray(int size) {
@@ -520,10 +562,6 @@
/** SSID of the network */
public String ssid;
- /** Network ID in wpa_supplicant */
- public int networkId;
- /** Assigned priority for the network */
- public int priority;
/** Bitmask of the FLAG_XXX */
public byte flags;
/** Bitmask of the ATUH_XXX */
@@ -580,8 +618,6 @@
dest.writeInt(networkList.length);
for (int i = 0; i < networkList.length; i++) {
dest.writeString(networkList[i].ssid);
- dest.writeInt(networkList[i].networkId);
- dest.writeInt(networkList[i].priority);
dest.writeByte(networkList[i].flags);
dest.writeByte(networkList[i].authBitField);
}
@@ -608,8 +644,6 @@
for (int i = 0; i < numNetworks; i++) {
String ssid = in.readString();
PnoNetwork network = new PnoNetwork(ssid);
- network.networkId = in.readInt();
- network.priority = in.readInt();
network.flags = in.readByte();
network.authBitField = in.readByte();
settings.networkList[i] = network;
diff --git a/wifi/java/android/net/wifi/nan/ConfigRequest.java b/wifi/java/android/net/wifi/nan/ConfigRequest.java
index 44544de..5d41579 100644
--- a/wifi/java/android/net/wifi/nan/ConfigRequest.java
+++ b/wifi/java/android/net/wifi/nan/ConfigRequest.java
@@ -30,7 +30,7 @@
*
* @hide PROPOSED_NAN_API
*/
-public class ConfigRequest implements Parcelable {
+public final class ConfigRequest implements Parcelable {
/**
* Lower range of possible cluster ID.
*
diff --git a/wifi/java/android/net/wifi/nan/PublishConfig.java b/wifi/java/android/net/wifi/nan/PublishConfig.java
index 71f99d9..6203f95 100644
--- a/wifi/java/android/net/wifi/nan/PublishConfig.java
+++ b/wifi/java/android/net/wifi/nan/PublishConfig.java
@@ -37,7 +37,7 @@
*
* @hide PROPOSED_NAN_API
*/
-public class PublishConfig implements Parcelable {
+public final class PublishConfig implements Parcelable {
/** @hide */
@IntDef({
PUBLISH_TYPE_UNSOLICITED, PUBLISH_TYPE_SOLICITED })
diff --git a/wifi/java/android/net/wifi/nan/SubscribeConfig.java b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
index 7904875..5a2c762 100644
--- a/wifi/java/android/net/wifi/nan/SubscribeConfig.java
+++ b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
@@ -37,7 +37,7 @@
*
* @hide PROPOSED_NAN_API
*/
-public class SubscribeConfig implements Parcelable {
+public final class SubscribeConfig implements Parcelable {
/** @hide */
@IntDef({
SUBSCRIBE_TYPE_PASSIVE, SUBSCRIBE_TYPE_ACTIVE })