Merge "Center align AoD2 notifications"
diff --git a/Android.bp b/Android.bp
index 2dc1cc3..5c1ccb7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -47,6 +47,7 @@
srcs: [
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
+ "tools/streaming_proto/stream.proto",
],
},
android: {
@@ -61,7 +62,7 @@
"core/proto/android/os/pagetypeinfo.proto",
"core/proto/android/os/procrank.proto",
"core/proto/android/service/graphicsstats.proto",
- "libs/incident/proto/android/privacy.proto",
+ "tools/streaming_proto/stream.proto",
],
shared: {
enabled: false,
@@ -70,11 +71,33 @@
},
}
+gensrcs {
+ name: "gen-platform-proto-constants",
+ depfile: true,
+
+ tools: [
+ "aprotoc",
+ "protoc-gen-cppstream",
+ ],
+
+ srcs: [
+ "core/proto/android/os/kernelwake.proto",
+ "core/proto/android/os/pagetypeinfo.proto",
+ "core/proto/android/os/procrank.proto",
+ ],
+
+ // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool
+ cmd: "PATH=$$PATH:$$(dirname $(location protoc-gen-cppstream)) $(location aprotoc) --plugin=protoc-gen-cpp-stream=$(location protoc-gen-cppstream) --dependency_out=$(depfile) --cppstream_out=$(genDir)/ -Iexternal/protobuf/src -I . $(in)",
+
+ output_extension = "proto.h",
+}
+
subdirs = [
"cmds/*",
- "core/jni",
+ "core/*",
"libs/*",
"media/*",
+ "proto",
"tools/*",
"native/android",
"native/graphics/jni",
diff --git a/Android.mk b/Android.mk
index c87aec4..1ed8a25 100644
--- a/Android.mk
+++ b/Android.mk
@@ -128,7 +128,7 @@
../../system/bt/binder/android/bluetooth/IBluetoothHeadsetPhone.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothHealth.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothHealthCallback.aidl \
- ../../system/bt/binder/android/bluetooth/IBluetoothInputDevice.aidl \
+ ../../system/bt/binder/android/bluetooth/IBluetoothHidHost.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothPan.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothManager.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothManagerCallback.aidl \
@@ -139,7 +139,7 @@
../../system/bt/binder/android/bluetooth/IBluetoothSap.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothStateChangeCallback.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothHeadsetClient.aidl \
- ../../system/bt/binder/android/bluetooth/IBluetoothInputHost.aidl \
+ ../../system/bt/binder/android/bluetooth/IBluetoothHidDevice.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothHidDeviceCallback.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothGatt.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothGattCallback.aidl \
@@ -270,6 +270,7 @@
core/java/android/os/IRecoverySystemProgressListener.aidl \
core/java/android/os/IRemoteCallback.aidl \
core/java/android/os/ISchedulingPolicyService.aidl \
+ core/java/android/os/IStatsCallbacks.aidl \
core/java/android/os/IStatsCompanionService.aidl \
core/java/android/os/IStatsManager.aidl \
core/java/android/os/IThermalEventListener.aidl \
@@ -379,7 +380,7 @@
core/java/android/speech/tts/ITextToSpeechService.aidl \
core/java/com/android/internal/app/IAppOpsCallback.aidl \
core/java/com/android/internal/app/IAppOpsService.aidl \
- core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl \
+ core/java/com/android/internal/app/IAssistDataReceiver.aidl \
core/java/com/android/internal/app/IBatteryStats.aidl \
core/java/com/android/internal/app/ISoundTriggerService.aidl \
core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
@@ -988,7 +989,8 @@
framework_docs_LOCAL_INTERMEDIATE_SOURCES := \
$(framework_res_source_path)/android/R.java \
$(framework_res_source_path)/android/Manifest.java \
- $(framework_res_source_path)/com/android/internal/R.java
+ $(framework_res_source_path)/com/android/internal/R.java \
+ $(patsubst $(TARGET_OUT_COMMON_INTERMEDIATES)/%,%,$(libcore_to_document_generated))
framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \
core-oj \
@@ -1059,7 +1061,7 @@
framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES := \
frameworks/base/docs/knowntags.txt \
- libcore/Docs.mk
+ $(libcore_to_document_generated)
samples_dir := development/samples/browseable
@@ -1551,6 +1553,7 @@
-Iexternal/protobuf/src
LOCAL_SOURCE_FILES_ALL_GENERATED := true
LOCAL_SRC_FILES := \
+ tools/streaming_proto/stream.proto \
$(call all-proto-files-under, core/proto) \
$(call all-proto-files-under, libs/incident/proto)
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index f27e8c8..4889941 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.perftests.core">
+ <uses-permission
+ android:name="android.permission.GET_ACCOUNTS" />
+
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.StubActivity" />
diff --git a/apct-tests/perftests/core/src/android/accounts/AccountManagerPerfTest.java b/apct-tests/perftests/core/src/android/accounts/AccountManagerPerfTest.java
new file mode 100644
index 0000000..b9411fa
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/accounts/AccountManagerPerfTest.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 android.accounts;
+
+import static junit.framework.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class AccountManagerPerfTest {
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void testGetAccounts() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getTargetContext();
+ if (context.checkSelfPermission(Manifest.permission.GET_ACCOUNTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ fail("Missing required GET_ACCOUNTS permission");
+ }
+ AccountManager accountManager = AccountManager.get(context);
+ while (state.keepRunning()) {
+ accountManager.getAccounts();
+ }
+ }
+}
diff --git a/api/current.txt b/api/current.txt
index 8b3c740..7074de5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -484,6 +484,7 @@
field public static final int detailSocialSummary = 16843428; // 0x10102a4
field public static final int detailsElementBackground = 16843598; // 0x101034e
field public static final int dial = 16843010; // 0x1010102
+ field public static final int dialogCornerRadius = 16844145; // 0x1010571
field public static final int dialogIcon = 16843252; // 0x10101f4
field public static final int dialogLayout = 16843255; // 0x10101f7
field public static final int dialogMessage = 16843251; // 0x10101f3
@@ -612,6 +613,7 @@
field public static final int fontProviderPackage = 16844119; // 0x1010557
field public static final int fontProviderQuery = 16844113; // 0x1010551
field public static final int fontStyle = 16844095; // 0x101053f
+ field public static final int fontVariationSettings = 16844144; // 0x1010570
field public static final int fontWeight = 16844083; // 0x1010533
field public static final int footerDividersEnabled = 16843311; // 0x101022f
field public static final int forceHasOverlappingRendering = 16844065; // 0x1010521
@@ -6328,6 +6330,7 @@
method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
+ method public int getLockTaskFeatures(android.content.ComponentName);
method public java.lang.String[] getLockTaskPackages(android.content.ComponentName);
method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName);
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -6414,6 +6417,7 @@
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
+ method public void setLockTaskFeatures(android.content.ComponentName, int);
method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException;
method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence);
method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
@@ -6530,6 +6534,13 @@
field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
field public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 8; // 0x8
field public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1; // 0x1
+ field public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 16; // 0x10
+ field public static final int LOCK_TASK_FEATURE_HOME = 4; // 0x4
+ field public static final int LOCK_TASK_FEATURE_KEYGUARD = 32; // 0x20
+ field public static final int LOCK_TASK_FEATURE_NONE = 0; // 0x0
+ field public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 2; // 0x2
+ field public static final int LOCK_TASK_FEATURE_RECENTS = 8; // 0x8
+ field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000
field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000
@@ -6825,6 +6836,7 @@
method public int getBackoffPolicy();
method public android.content.ClipData getClipData();
method public int getClipGrantFlags();
+ method public long getEstimatedNetworkBytes();
method public android.os.PersistableBundle getExtras();
method public long getFlexMillis();
method public int getId();
@@ -6852,6 +6864,7 @@
field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+ field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
field public static final int NETWORK_TYPE_METERED = 4; // 0x4
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
@@ -6865,6 +6878,7 @@
method public android.app.job.JobInfo build();
method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
+ method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
@@ -6899,6 +6913,7 @@
method public int getClipGrantFlags();
method public android.os.PersistableBundle getExtras();
method public int getJobId();
+ method public android.net.Network getNetwork();
method public android.os.Bundle getTransientExtras();
method public java.lang.String[] getTriggeredContentAuthorities();
method public android.net.Uri[] getTriggeredContentUris();
@@ -6938,8 +6953,10 @@
public final class JobWorkItem implements android.os.Parcelable {
ctor public JobWorkItem(android.content.Intent);
+ ctor public JobWorkItem(android.content.Intent, long);
method public int describeContents();
method public int getDeliveryCount();
+ method public long getEstimatedNetworkBytes();
method public android.content.Intent getIntent();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
@@ -7029,6 +7046,27 @@
}
+package android.app.slice.widget {
+
+ public class SliceView extends android.view.ViewGroup {
+ ctor public SliceView(android.content.Context);
+ ctor public SliceView(android.content.Context, android.util.AttributeSet);
+ ctor public SliceView(android.content.Context, android.util.AttributeSet, int);
+ ctor public SliceView(android.content.Context, android.util.AttributeSet, int, int);
+ method public void clearSlice();
+ method public java.lang.String getMode();
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void setMode(java.lang.String);
+ method public void setScrollable(boolean);
+ method public boolean setSlice(android.net.Uri);
+ method public void showSlice(android.app.slice.Slice);
+ field public static final java.lang.String MODE_LARGE = "SLICE_LARGE";
+ field public static final java.lang.String MODE_SHORTCUT = "SLICE_ICON";
+ field public static final java.lang.String MODE_SMALL = "SLICE_SMALL";
+ }
+
+}
+
package android.app.usage {
public final class ConfigurationStats implements android.os.Parcelable {
@@ -11979,6 +12017,7 @@
method public java.lang.String[] getColumnNames();
method public int getCount();
method public android.database.sqlite.SQLiteDatabase getDatabase();
+ method public void setFillWindowForwardOnly(boolean);
method public void setSelectionArguments(java.lang.String[]);
}
@@ -18233,13 +18272,11 @@
method public synchronized void applyLocalizedPattern(java.lang.String);
method public synchronized void applyPattern(java.lang.String);
method public synchronized boolean areSignificantDigitsUsed();
- method public synchronized boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
- method public synchronized android.icu.util.Currency getCurrency();
method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo();
method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage();
method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols();
@@ -18247,12 +18284,8 @@
method public synchronized int getGroupingSize();
method public synchronized java.math.MathContext getMathContext();
method public synchronized android.icu.math.MathContext getMathContextICU();
- method public synchronized int getMaximumFractionDigits();
- method public synchronized int getMaximumIntegerDigits();
method public synchronized int getMaximumSignificantDigits();
method public synchronized byte getMinimumExponentDigits();
- method public synchronized int getMinimumFractionDigits();
- method public synchronized int getMinimumIntegerDigits();
method public synchronized int getMinimumSignificantDigits();
method public synchronized int getMultiplier();
method public synchronized java.lang.String getNegativePrefix();
@@ -18263,19 +18296,13 @@
method public synchronized java.lang.String getPositivePrefix();
method public synchronized java.lang.String getPositiveSuffix();
method public synchronized java.math.BigDecimal getRoundingIncrement();
- method public synchronized int getRoundingMode();
method public synchronized int getSecondaryGroupingSize();
- method public synchronized int hashCode();
method public synchronized boolean isDecimalPatternMatchRequired();
method public synchronized boolean isDecimalSeparatorAlwaysShown();
method public synchronized boolean isExponentSignAlwaysShown();
- method public synchronized boolean isGroupingUsed();
method public synchronized boolean isParseBigDecimal();
- method public synchronized boolean isParseIntegerOnly();
- method public synchronized boolean isParseStrict();
method public synchronized boolean isScientificNotation();
method public java.lang.Number parse(java.lang.String, java.text.ParsePosition);
- method public synchronized void setCurrency(android.icu.util.Currency);
method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo);
method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage);
method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols);
@@ -18284,15 +18311,10 @@
method public synchronized void setExponentSignAlwaysShown(boolean);
method public synchronized void setFormatWidth(int);
method public synchronized void setGroupingSize(int);
- method public synchronized void setGroupingUsed(boolean);
method public synchronized void setMathContext(java.math.MathContext);
method public synchronized void setMathContextICU(android.icu.math.MathContext);
- method public synchronized void setMaximumFractionDigits(int);
- method public synchronized void setMaximumIntegerDigits(int);
method public synchronized void setMaximumSignificantDigits(int);
method public synchronized void setMinimumExponentDigits(byte);
- method public synchronized void setMinimumFractionDigits(int);
- method public synchronized void setMinimumIntegerDigits(int);
method public synchronized void setMinimumSignificantDigits(int);
method public synchronized void setMultiplier(int);
method public synchronized void setNegativePrefix(java.lang.String);
@@ -18300,15 +18322,12 @@
method public synchronized void setPadCharacter(char);
method public synchronized void setPadPosition(int);
method public synchronized void setParseBigDecimal(boolean);
- method public synchronized void setParseIntegerOnly(boolean);
method public deprecated void setParseMaxDigits(int);
- method public synchronized void setParseStrict(boolean);
method public synchronized void setPositivePrefix(java.lang.String);
method public synchronized void setPositiveSuffix(java.lang.String);
method public synchronized void setRoundingIncrement(java.math.BigDecimal);
method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal);
method public synchronized void setRoundingIncrement(double);
- method public synchronized void setRoundingMode(int);
method public synchronized void setScientificNotation(boolean);
method public synchronized void setSecondaryGroupingSize(int);
method public synchronized void setSignificantDigitsUsed(boolean);
@@ -22779,6 +22798,10 @@
field public static final java.lang.String KEY_DURATION = "durationUs";
field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
+ field public static final java.lang.String KEY_GRID_COLS = "grid-cols";
+ field public static final java.lang.String KEY_GRID_HEIGHT = "grid-height";
+ field public static final java.lang.String KEY_GRID_ROWS = "grid-rows";
+ field public static final java.lang.String KEY_GRID_WIDTH = "grid-width";
field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info";
field public static final java.lang.String KEY_HEIGHT = "height";
field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
@@ -22822,6 +22845,7 @@
field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw";
field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
+ field public static final java.lang.String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt";
field public static final java.lang.String MIMETYPE_VIDEO_AVC = "video/avc";
@@ -22914,9 +22938,13 @@
ctor public MediaMetadataRetriever();
method public java.lang.String extractMetadata(int);
method public byte[] getEmbeddedPicture();
+ method public android.graphics.Bitmap getFrameAtIndex(int);
method public android.graphics.Bitmap getFrameAtTime(long, int);
method public android.graphics.Bitmap getFrameAtTime(long);
method public android.graphics.Bitmap getFrameAtTime();
+ method public android.graphics.Bitmap[] getFramesAtIndex(int, int);
+ method public android.graphics.Bitmap getImageAtIndex(int);
+ method public android.graphics.Bitmap getPrimaryImage();
method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
method public void release();
method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
@@ -22939,11 +22967,18 @@
field public static final int METADATA_KEY_DURATION = 9; // 0x9
field public static final int METADATA_KEY_GENRE = 6; // 0x6
field public static final int METADATA_KEY_HAS_AUDIO = 16; // 0x10
+ field public static final int METADATA_KEY_HAS_IMAGE = 26; // 0x1a
field public static final int METADATA_KEY_HAS_VIDEO = 17; // 0x11
+ field public static final int METADATA_KEY_IMAGE_COUNT = 27; // 0x1b
+ field public static final int METADATA_KEY_IMAGE_HEIGHT = 30; // 0x1e
+ field public static final int METADATA_KEY_IMAGE_PRIMARY = 28; // 0x1c
+ field public static final int METADATA_KEY_IMAGE_ROTATION = 31; // 0x1f
+ field public static final int METADATA_KEY_IMAGE_WIDTH = 29; // 0x1d
field public static final int METADATA_KEY_LOCATION = 23; // 0x17
field public static final int METADATA_KEY_MIMETYPE = 12; // 0xc
field public static final int METADATA_KEY_NUM_TRACKS = 10; // 0xa
field public static final int METADATA_KEY_TITLE = 7; // 0x7
+ field public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32; // 0x20
field public static final int METADATA_KEY_VIDEO_HEIGHT = 19; // 0x13
field public static final int METADATA_KEY_VIDEO_ROTATION = 24; // 0x18
field public static final int METADATA_KEY_VIDEO_WIDTH = 18; // 0x12
@@ -25781,7 +25816,6 @@
public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
method public void close();
- method protected void finalize();
method public int getSpi();
}
@@ -31917,6 +31951,7 @@
field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
+ field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -37315,6 +37350,7 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+ field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2
field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
}
@@ -37322,6 +37358,7 @@
ctor public FillResponse.Builder();
method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
method public android.service.autofill.FillResponse build();
+ method public android.service.autofill.FillResponse.Builder disableAutofill(long);
method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
method public android.service.autofill.FillResponse.Builder setFlags(int);
@@ -39867,12 +39904,14 @@
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_MODE_INT = "cdma_roaming_mode_int";
field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
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_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ field public static final java.lang.String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
@@ -39886,6 +39925,7 @@
field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
+ field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
@@ -39977,6 +40017,8 @@
method public int getLatitude();
method public int getLongitude();
method public int getNetworkId();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public int getSystemId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
@@ -39988,8 +40030,13 @@
method public int getBsic();
method public int getCid();
method public int getLac();
- method public int getMcc();
- method public int getMnc();
+ method public deprecated int getMcc();
+ method public java.lang.String getMccStr();
+ method public deprecated int getMnc();
+ method public java.lang.String getMncStr();
+ method public java.lang.String getMobileNetworkOperator();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public deprecated int getPsc();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
@@ -39999,8 +40046,13 @@
method public int describeContents();
method public int getCi();
method public int getEarfcn();
- method public int getMcc();
- method public int getMnc();
+ method public deprecated int getMcc();
+ method public java.lang.String getMccStr();
+ method public deprecated int getMnc();
+ method public java.lang.String getMncStr();
+ method public java.lang.String getMobileNetworkOperator();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public int getPci();
method public int getTac();
method public void writeToParcel(android.os.Parcel, int);
@@ -40011,8 +40063,13 @@
method public int describeContents();
method public int getCid();
method public int getLac();
- method public int getMcc();
- method public int getMnc();
+ method public deprecated int getMcc();
+ method public java.lang.String getMccStr();
+ method public deprecated int getMnc();
+ method public java.lang.String getMncStr();
+ method public java.lang.String getMobileNetworkOperator();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public int getPsc();
method public int getUarfcn();
method public void writeToParcel(android.os.Parcel, int);
@@ -40445,6 +40502,8 @@
field public static final int ENCODING_7BIT = 1; // 0x1
field public static final int ENCODING_8BIT = 2; // 0x2
field public static final int ENCODING_UNKNOWN = 0; // 0x0
+ field public static final java.lang.String FORMAT_3GPP = "3gpp";
+ field public static final java.lang.String FORMAT_3GPP2 = "3gpp2";
field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c
field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86
field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0
@@ -40601,6 +40660,10 @@
field public static final int CALL_STATE_IDLE = 0; // 0x0
field public static final int CALL_STATE_OFFHOOK = 2; // 0x2
field public static final int CALL_STATE_RINGING = 1; // 0x1
+ field public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1
+ field public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2
+ field public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0
+ field public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1; // 0xffffffff
field public static final int DATA_ACTIVITY_DORMANT = 4; // 0x4
field public static final int DATA_ACTIVITY_IN = 1; // 0x1
field public static final int DATA_ACTIVITY_INOUT = 3; // 0x3
@@ -52072,7 +52135,6 @@
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
- method public void invalidate(int, int, int, int);
method public boolean isAllCaps();
method public boolean isCursorVisible();
method public boolean isElegantTextHeight();
diff --git a/api/system-current.txt b/api/system-current.txt
index ee23b6d..af15a00 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -88,6 +88,7 @@
field public static final java.lang.String CAPTURE_SECURE_VIDEO_OUTPUT = "android.permission.CAPTURE_SECURE_VIDEO_OUTPUT";
field public static final java.lang.String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
field public static final java.lang.String CAPTURE_VIDEO_OUTPUT = "android.permission.CAPTURE_VIDEO_OUTPUT";
+ field public static final java.lang.String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
field public static final java.lang.String CHANGE_COMPONENT_ENABLED_STATE = "android.permission.CHANGE_COMPONENT_ENABLED_STATE";
field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION";
field public static final java.lang.String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
@@ -616,6 +617,7 @@
field public static final int detailSocialSummary = 16843428; // 0x10102a4
field public static final int detailsElementBackground = 16843598; // 0x101034e
field public static final int dial = 16843010; // 0x1010102
+ field public static final int dialogCornerRadius = 16844145; // 0x1010571
field public static final int dialogIcon = 16843252; // 0x10101f4
field public static final int dialogLayout = 16843255; // 0x10101f7
field public static final int dialogMessage = 16843251; // 0x10101f3
@@ -744,6 +746,7 @@
field public static final int fontProviderPackage = 16844119; // 0x1010557
field public static final int fontProviderQuery = 16844113; // 0x1010551
field public static final int fontStyle = 16844095; // 0x101053f
+ field public static final int fontVariationSettings = 16844144; // 0x1010570
field public static final int fontWeight = 16844083; // 0x1010533
field public static final int footerDividersEnabled = 16843311; // 0x101022f
field public static final int forceHasOverlappingRendering = 16844065; // 0x1010521
@@ -6550,6 +6553,7 @@
method public java.lang.CharSequence getDeviceOwnerOrganizationName();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
+ method public int getLockTaskFeatures(android.content.ComponentName);
method public java.lang.String[] getLockTaskPackages(android.content.ComponentName);
method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName);
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -6649,6 +6653,7 @@
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
+ method public void setLockTaskFeatures(android.content.ComponentName, int);
method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException;
method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence);
method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
@@ -6776,6 +6781,13 @@
field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
field public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 8; // 0x8
field public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1; // 0x1
+ field public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 16; // 0x10
+ field public static final int LOCK_TASK_FEATURE_HOME = 4; // 0x4
+ field public static final int LOCK_TASK_FEATURE_KEYGUARD = 32; // 0x20
+ field public static final int LOCK_TASK_FEATURE_NONE = 0; // 0x0
+ field public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 2; // 0x2
+ field public static final int LOCK_TASK_FEATURE_RECENTS = 8; // 0x8
+ field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000
field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000
@@ -7267,6 +7279,7 @@
method public int getBackoffPolicy();
method public android.content.ClipData getClipData();
method public int getClipGrantFlags();
+ method public long getEstimatedNetworkBytes();
method public android.os.PersistableBundle getExtras();
method public long getFlexMillis();
method public int getId();
@@ -7294,6 +7307,7 @@
field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+ field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
field public static final int NETWORK_TYPE_METERED = 4; // 0x4
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
@@ -7307,6 +7321,7 @@
method public android.app.job.JobInfo build();
method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
+ method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
@@ -7341,6 +7356,7 @@
method public int getClipGrantFlags();
method public android.os.PersistableBundle getExtras();
method public int getJobId();
+ method public android.net.Network getNetwork();
method public android.os.Bundle getTransientExtras();
method public java.lang.String[] getTriggeredContentAuthorities();
method public android.net.Uri[] getTriggeredContentUris();
@@ -7381,8 +7397,10 @@
public final class JobWorkItem implements android.os.Parcelable {
ctor public JobWorkItem(android.content.Intent);
+ ctor public JobWorkItem(android.content.Intent, long);
method public int describeContents();
method public int getDeliveryCount();
+ method public long getEstimatedNetworkBytes();
method public android.content.Intent getIntent();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
@@ -7472,6 +7490,27 @@
}
+package android.app.slice.widget {
+
+ public class SliceView extends android.view.ViewGroup {
+ ctor public SliceView(android.content.Context);
+ ctor public SliceView(android.content.Context, android.util.AttributeSet);
+ ctor public SliceView(android.content.Context, android.util.AttributeSet, int);
+ ctor public SliceView(android.content.Context, android.util.AttributeSet, int, int);
+ method public void clearSlice();
+ method public java.lang.String getMode();
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void setMode(java.lang.String);
+ method public void setScrollable(boolean);
+ method public boolean setSlice(android.net.Uri);
+ method public void showSlice(android.app.slice.Slice);
+ field public static final java.lang.String MODE_LARGE = "SLICE_LARGE";
+ field public static final java.lang.String MODE_SHORTCUT = "SLICE_ICON";
+ field public static final java.lang.String MODE_SMALL = "SLICE_SMALL";
+ }
+
+}
+
package android.app.usage {
public final class CacheQuotaHint implements android.os.Parcelable {
@@ -7639,6 +7678,7 @@
method public java.util.List<android.app.usage.ConfigurationStats> queryConfigurations(int, long, long);
method public android.app.usage.UsageEvents queryEvents(long, long);
method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
+ method public void setAppStandbyBucket(java.lang.String, int);
method public void whitelistAppTemporarily(java.lang.String, long, android.os.UserHandle);
field public static final int INTERVAL_BEST = 4; // 0x4
field public static final int INTERVAL_DAILY = 0; // 0x0
@@ -10197,6 +10237,7 @@
field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
field public static final java.lang.String EXTRA_QUICK_VIEW_FEATURES = "android.intent.extra.QUICK_VIEW_FEATURES";
field public static final java.lang.String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE";
+ field public static final java.lang.String EXTRA_REASON = "android.intent.extra.REASON";
field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER";
field public static final java.lang.String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME";
field public static final java.lang.String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
@@ -12723,6 +12764,7 @@
method public java.lang.String[] getColumnNames();
method public int getCount();
method public android.database.sqlite.SQLiteDatabase getDatabase();
+ method public void setFillWindowForwardOnly(boolean);
method public void setSelectionArguments(java.lang.String[]);
}
@@ -19789,13 +19831,11 @@
method public synchronized void applyLocalizedPattern(java.lang.String);
method public synchronized void applyPattern(java.lang.String);
method public synchronized boolean areSignificantDigitsUsed();
- method public synchronized boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
- method public synchronized android.icu.util.Currency getCurrency();
method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo();
method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage();
method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols();
@@ -19803,12 +19843,8 @@
method public synchronized int getGroupingSize();
method public synchronized java.math.MathContext getMathContext();
method public synchronized android.icu.math.MathContext getMathContextICU();
- method public synchronized int getMaximumFractionDigits();
- method public synchronized int getMaximumIntegerDigits();
method public synchronized int getMaximumSignificantDigits();
method public synchronized byte getMinimumExponentDigits();
- method public synchronized int getMinimumFractionDigits();
- method public synchronized int getMinimumIntegerDigits();
method public synchronized int getMinimumSignificantDigits();
method public synchronized int getMultiplier();
method public synchronized java.lang.String getNegativePrefix();
@@ -19819,19 +19855,13 @@
method public synchronized java.lang.String getPositivePrefix();
method public synchronized java.lang.String getPositiveSuffix();
method public synchronized java.math.BigDecimal getRoundingIncrement();
- method public synchronized int getRoundingMode();
method public synchronized int getSecondaryGroupingSize();
- method public synchronized int hashCode();
method public synchronized boolean isDecimalPatternMatchRequired();
method public synchronized boolean isDecimalSeparatorAlwaysShown();
method public synchronized boolean isExponentSignAlwaysShown();
- method public synchronized boolean isGroupingUsed();
method public synchronized boolean isParseBigDecimal();
- method public synchronized boolean isParseIntegerOnly();
- method public synchronized boolean isParseStrict();
method public synchronized boolean isScientificNotation();
method public java.lang.Number parse(java.lang.String, java.text.ParsePosition);
- method public synchronized void setCurrency(android.icu.util.Currency);
method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo);
method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage);
method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols);
@@ -19840,15 +19870,10 @@
method public synchronized void setExponentSignAlwaysShown(boolean);
method public synchronized void setFormatWidth(int);
method public synchronized void setGroupingSize(int);
- method public synchronized void setGroupingUsed(boolean);
method public synchronized void setMathContext(java.math.MathContext);
method public synchronized void setMathContextICU(android.icu.math.MathContext);
- method public synchronized void setMaximumFractionDigits(int);
- method public synchronized void setMaximumIntegerDigits(int);
method public synchronized void setMaximumSignificantDigits(int);
method public synchronized void setMinimumExponentDigits(byte);
- method public synchronized void setMinimumFractionDigits(int);
- method public synchronized void setMinimumIntegerDigits(int);
method public synchronized void setMinimumSignificantDigits(int);
method public synchronized void setMultiplier(int);
method public synchronized void setNegativePrefix(java.lang.String);
@@ -19856,15 +19881,12 @@
method public synchronized void setPadCharacter(char);
method public synchronized void setPadPosition(int);
method public synchronized void setParseBigDecimal(boolean);
- method public synchronized void setParseIntegerOnly(boolean);
method public deprecated void setParseMaxDigits(int);
- method public synchronized void setParseStrict(boolean);
method public synchronized void setPositivePrefix(java.lang.String);
method public synchronized void setPositiveSuffix(java.lang.String);
method public synchronized void setRoundingIncrement(java.math.BigDecimal);
method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal);
method public synchronized void setRoundingIncrement(double);
- method public synchronized void setRoundingMode(int);
method public synchronized void setScientificNotation(boolean);
method public synchronized void setSecondaryGroupingSize(int);
method public synchronized void setSignificantDigitsUsed(boolean);
@@ -24669,6 +24691,10 @@
field public static final java.lang.String KEY_DURATION = "durationUs";
field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
+ field public static final java.lang.String KEY_GRID_COLS = "grid-cols";
+ field public static final java.lang.String KEY_GRID_HEIGHT = "grid-height";
+ field public static final java.lang.String KEY_GRID_ROWS = "grid-rows";
+ field public static final java.lang.String KEY_GRID_WIDTH = "grid-width";
field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info";
field public static final java.lang.String KEY_HEIGHT = "height";
field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
@@ -24712,6 +24738,7 @@
field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw";
field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
+ field public static final java.lang.String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt";
field public static final java.lang.String MIMETYPE_VIDEO_AVC = "video/avc";
@@ -24804,9 +24831,13 @@
ctor public MediaMetadataRetriever();
method public java.lang.String extractMetadata(int);
method public byte[] getEmbeddedPicture();
+ method public android.graphics.Bitmap getFrameAtIndex(int);
method public android.graphics.Bitmap getFrameAtTime(long, int);
method public android.graphics.Bitmap getFrameAtTime(long);
method public android.graphics.Bitmap getFrameAtTime();
+ method public android.graphics.Bitmap[] getFramesAtIndex(int, int);
+ method public android.graphics.Bitmap getImageAtIndex(int);
+ method public android.graphics.Bitmap getPrimaryImage();
method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
method public void release();
method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
@@ -24829,11 +24860,18 @@
field public static final int METADATA_KEY_DURATION = 9; // 0x9
field public static final int METADATA_KEY_GENRE = 6; // 0x6
field public static final int METADATA_KEY_HAS_AUDIO = 16; // 0x10
+ field public static final int METADATA_KEY_HAS_IMAGE = 26; // 0x1a
field public static final int METADATA_KEY_HAS_VIDEO = 17; // 0x11
+ field public static final int METADATA_KEY_IMAGE_COUNT = 27; // 0x1b
+ field public static final int METADATA_KEY_IMAGE_HEIGHT = 30; // 0x1e
+ field public static final int METADATA_KEY_IMAGE_PRIMARY = 28; // 0x1c
+ field public static final int METADATA_KEY_IMAGE_ROTATION = 31; // 0x1f
+ field public static final int METADATA_KEY_IMAGE_WIDTH = 29; // 0x1d
field public static final int METADATA_KEY_LOCATION = 23; // 0x17
field public static final int METADATA_KEY_MIMETYPE = 12; // 0xc
field public static final int METADATA_KEY_NUM_TRACKS = 10; // 0xa
field public static final int METADATA_KEY_TITLE = 7; // 0x7
+ field public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32; // 0x20
field public static final int METADATA_KEY_VIDEO_HEIGHT = 19; // 0x13
field public static final int METADATA_KEY_VIDEO_ROTATION = 24; // 0x18
field public static final int METADATA_KEY_VIDEO_WIDTH = 18; // 0x12
@@ -28023,7 +28061,6 @@
public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
method public void close();
- method protected void finalize();
method public int getSpi();
}
@@ -34766,6 +34803,7 @@
field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
+ field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -40417,6 +40455,7 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+ field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2
field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
}
@@ -40424,6 +40463,7 @@
ctor public FillResponse.Builder();
method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
method public android.service.autofill.FillResponse build();
+ method public android.service.autofill.FillResponse.Builder disableAutofill(long);
method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
method public android.service.autofill.FillResponse.Builder setFlags(int);
@@ -43382,6 +43422,7 @@
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_MODE_INT = "cdma_roaming_mode_int";
field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
@@ -43389,6 +43430,7 @@
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_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
field public static final java.lang.String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
+ field public static final java.lang.String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
@@ -43402,6 +43444,7 @@
field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
+ field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
@@ -43493,6 +43536,8 @@
method public int getLatitude();
method public int getLongitude();
method public int getNetworkId();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public int getSystemId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
@@ -43504,8 +43549,13 @@
method public int getBsic();
method public int getCid();
method public int getLac();
- method public int getMcc();
- method public int getMnc();
+ method public deprecated int getMcc();
+ method public java.lang.String getMccStr();
+ method public deprecated int getMnc();
+ method public java.lang.String getMncStr();
+ method public java.lang.String getMobileNetworkOperator();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public deprecated int getPsc();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
@@ -43515,8 +43565,13 @@
method public int describeContents();
method public int getCi();
method public int getEarfcn();
- method public int getMcc();
- method public int getMnc();
+ method public deprecated int getMcc();
+ method public java.lang.String getMccStr();
+ method public deprecated int getMnc();
+ method public java.lang.String getMncStr();
+ method public java.lang.String getMobileNetworkOperator();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public int getPci();
method public int getTac();
method public void writeToParcel(android.os.Parcel, int);
@@ -43527,8 +43582,13 @@
method public int describeContents();
method public int getCid();
method public int getLac();
- method public int getMcc();
- method public int getMnc();
+ method public deprecated int getMcc();
+ method public java.lang.String getMccStr();
+ method public deprecated int getMnc();
+ method public java.lang.String getMncStr();
+ method public java.lang.String getMobileNetworkOperator();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public int getPsc();
method public int getUarfcn();
method public void writeToParcel(android.os.Parcel, int);
@@ -43965,6 +44025,8 @@
field public static final int ENCODING_7BIT = 1; // 0x1
field public static final int ENCODING_8BIT = 2; // 0x2
field public static final int ENCODING_UNKNOWN = 0; // 0x0
+ field public static final java.lang.String FORMAT_3GPP = "3gpp";
+ field public static final java.lang.String FORMAT_3GPP2 = "3gpp2";
field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c
field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86
field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0
@@ -44223,6 +44285,10 @@
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1
+ field public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2
+ field public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0
+ field public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1; // 0xffffffff
field public static final int DATA_ACTIVITY_DORMANT = 4; // 0x4
field public static final int DATA_ACTIVITY_IN = 1; // 0x1
field public static final int DATA_ACTIVITY_INOUT = 3; // 0x3
@@ -56176,7 +56242,6 @@
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
- method public void invalidate(int, int, int, int);
method public boolean isAllCaps();
method public boolean isCursorVisible();
method public boolean isElegantTextHeight();
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 639877f..e9afa70 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -330,7 +330,6 @@
method public deprecated java.util.List<android.net.wifi.BatchedScanResult> getBatchedScanResults();
method public android.net.wifi.WifiConnectionStatistics getConnectionStatistics();
method public deprecated boolean isBatchedScanSupported();
- method public deprecated boolean setWifiApEnabled(android.net.wifi.WifiConfiguration, boolean);
method public deprecated boolean startLocationRestrictedScan(android.os.WorkSource);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 9a241ad..0bc0cd9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -484,6 +484,7 @@
field public static final int detailSocialSummary = 16843428; // 0x10102a4
field public static final int detailsElementBackground = 16843598; // 0x101034e
field public static final int dial = 16843010; // 0x1010102
+ field public static final int dialogCornerRadius = 16844145; // 0x1010571
field public static final int dialogIcon = 16843252; // 0x10101f4
field public static final int dialogLayout = 16843255; // 0x10101f7
field public static final int dialogMessage = 16843251; // 0x10101f3
@@ -612,6 +613,7 @@
field public static final int fontProviderPackage = 16844119; // 0x1010557
field public static final int fontProviderQuery = 16844113; // 0x1010551
field public static final int fontStyle = 16844095; // 0x101053f
+ field public static final int fontVariationSettings = 16844144; // 0x1010570
field public static final int fontWeight = 16844083; // 0x1010533
field public static final int footerDividersEnabled = 16843311; // 0x101022f
field public static final int forceHasOverlappingRendering = 16844065; // 0x1010521
@@ -3872,6 +3874,7 @@
method public void resizeStack(int, android.graphics.Rect) throws java.lang.SecurityException;
method public deprecated void restartPackage(java.lang.String);
method public void setTaskWindowingMode(int, int, boolean) throws java.lang.SecurityException;
+ method public void setTaskWindowingModeSplitScreenPrimary(int, int, boolean, boolean, android.graphics.Rect) throws java.lang.SecurityException;
method public static void setVrThread(int);
method public void setWatchHeapLimit(long);
method public static boolean supportsMultiWindow(android.content.Context);
@@ -3885,6 +3888,8 @@
field public static final int MOVE_TASK_WITH_HOME = 1; // 0x1
field public static final int RECENT_IGNORE_UNAVAILABLE = 2; // 0x2
field public static final int RECENT_WITH_EXCLUDED = 1; // 0x1
+ field public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1; // 0x1
+ field public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0; // 0x0
}
public static class ActivityManager.AppTask {
@@ -6393,6 +6398,7 @@
method public long getLastBugReportRequestTime();
method public long getLastNetworkLogRetrievalTime();
method public long getLastSecurityLogRetrievalTime();
+ method public int getLockTaskFeatures(android.content.ComponentName);
method public java.lang.String[] getLockTaskPackages(android.content.ComponentName);
method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName);
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -6482,6 +6488,7 @@
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
+ method public void setLockTaskFeatures(android.content.ComponentName, int);
method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException;
method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence);
method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
@@ -6601,6 +6608,13 @@
field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
field public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 8; // 0x8
field public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1; // 0x1
+ field public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 16; // 0x10
+ field public static final int LOCK_TASK_FEATURE_HOME = 4; // 0x4
+ field public static final int LOCK_TASK_FEATURE_KEYGUARD = 32; // 0x20
+ field public static final int LOCK_TASK_FEATURE_NONE = 0; // 0x0
+ field public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 2; // 0x2
+ field public static final int LOCK_TASK_FEATURE_RECENTS = 8; // 0x8
+ field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000
field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000
@@ -6896,6 +6910,7 @@
method public int getBackoffPolicy();
method public android.content.ClipData getClipData();
method public int getClipGrantFlags();
+ method public long getEstimatedNetworkBytes();
method public android.os.PersistableBundle getExtras();
method public long getFlexMillis();
method public int getId();
@@ -6923,6 +6938,7 @@
field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+ field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
field public static final int NETWORK_TYPE_METERED = 4; // 0x4
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
@@ -6936,6 +6952,7 @@
method public android.app.job.JobInfo build();
method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
+ method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
@@ -6970,6 +6987,7 @@
method public int getClipGrantFlags();
method public android.os.PersistableBundle getExtras();
method public int getJobId();
+ method public android.net.Network getNetwork();
method public android.os.Bundle getTransientExtras();
method public java.lang.String[] getTriggeredContentAuthorities();
method public android.net.Uri[] getTriggeredContentUris();
@@ -7009,8 +7027,10 @@
public final class JobWorkItem implements android.os.Parcelable {
ctor public JobWorkItem(android.content.Intent);
+ ctor public JobWorkItem(android.content.Intent, long);
method public int describeContents();
method public int getDeliveryCount();
+ method public long getEstimatedNetworkBytes();
method public android.content.Intent getIntent();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
@@ -7100,6 +7120,27 @@
}
+package android.app.slice.widget {
+
+ public class SliceView extends android.view.ViewGroup {
+ ctor public SliceView(android.content.Context);
+ ctor public SliceView(android.content.Context, android.util.AttributeSet);
+ ctor public SliceView(android.content.Context, android.util.AttributeSet, int);
+ ctor public SliceView(android.content.Context, android.util.AttributeSet, int, int);
+ method public void clearSlice();
+ method public java.lang.String getMode();
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void setMode(java.lang.String);
+ method public void setScrollable(boolean);
+ method public boolean setSlice(android.net.Uri);
+ method public void showSlice(android.app.slice.Slice);
+ field public static final java.lang.String MODE_LARGE = "SLICE_LARGE";
+ field public static final java.lang.String MODE_SHORTCUT = "SLICE_ICON";
+ field public static final java.lang.String MODE_SMALL = "SLICE_SMALL";
+ }
+
+}
+
package android.app.usage {
public final class ConfigurationStats implements android.os.Parcelable {
@@ -12066,6 +12107,7 @@
method public java.lang.String[] getColumnNames();
method public int getCount();
method public android.database.sqlite.SQLiteDatabase getDatabase();
+ method public void setFillWindowForwardOnly(boolean);
method public void setSelectionArguments(java.lang.String[]);
}
@@ -18375,13 +18417,11 @@
method public synchronized void applyLocalizedPattern(java.lang.String);
method public synchronized void applyPattern(java.lang.String);
method public synchronized boolean areSignificantDigitsUsed();
- method public synchronized boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
- method public synchronized android.icu.util.Currency getCurrency();
method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo();
method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage();
method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols();
@@ -18389,12 +18429,8 @@
method public synchronized int getGroupingSize();
method public synchronized java.math.MathContext getMathContext();
method public synchronized android.icu.math.MathContext getMathContextICU();
- method public synchronized int getMaximumFractionDigits();
- method public synchronized int getMaximumIntegerDigits();
method public synchronized int getMaximumSignificantDigits();
method public synchronized byte getMinimumExponentDigits();
- method public synchronized int getMinimumFractionDigits();
- method public synchronized int getMinimumIntegerDigits();
method public synchronized int getMinimumSignificantDigits();
method public synchronized int getMultiplier();
method public synchronized java.lang.String getNegativePrefix();
@@ -18405,19 +18441,13 @@
method public synchronized java.lang.String getPositivePrefix();
method public synchronized java.lang.String getPositiveSuffix();
method public synchronized java.math.BigDecimal getRoundingIncrement();
- method public synchronized int getRoundingMode();
method public synchronized int getSecondaryGroupingSize();
- method public synchronized int hashCode();
method public synchronized boolean isDecimalPatternMatchRequired();
method public synchronized boolean isDecimalSeparatorAlwaysShown();
method public synchronized boolean isExponentSignAlwaysShown();
- method public synchronized boolean isGroupingUsed();
method public synchronized boolean isParseBigDecimal();
- method public synchronized boolean isParseIntegerOnly();
- method public synchronized boolean isParseStrict();
method public synchronized boolean isScientificNotation();
method public java.lang.Number parse(java.lang.String, java.text.ParsePosition);
- method public synchronized void setCurrency(android.icu.util.Currency);
method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo);
method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage);
method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols);
@@ -18426,15 +18456,10 @@
method public synchronized void setExponentSignAlwaysShown(boolean);
method public synchronized void setFormatWidth(int);
method public synchronized void setGroupingSize(int);
- method public synchronized void setGroupingUsed(boolean);
method public synchronized void setMathContext(java.math.MathContext);
method public synchronized void setMathContextICU(android.icu.math.MathContext);
- method public synchronized void setMaximumFractionDigits(int);
- method public synchronized void setMaximumIntegerDigits(int);
method public synchronized void setMaximumSignificantDigits(int);
method public synchronized void setMinimumExponentDigits(byte);
- method public synchronized void setMinimumFractionDigits(int);
- method public synchronized void setMinimumIntegerDigits(int);
method public synchronized void setMinimumSignificantDigits(int);
method public synchronized void setMultiplier(int);
method public synchronized void setNegativePrefix(java.lang.String);
@@ -18442,15 +18467,12 @@
method public synchronized void setPadCharacter(char);
method public synchronized void setPadPosition(int);
method public synchronized void setParseBigDecimal(boolean);
- method public synchronized void setParseIntegerOnly(boolean);
method public deprecated void setParseMaxDigits(int);
- method public synchronized void setParseStrict(boolean);
method public synchronized void setPositivePrefix(java.lang.String);
method public synchronized void setPositiveSuffix(java.lang.String);
method public synchronized void setRoundingIncrement(java.math.BigDecimal);
method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal);
method public synchronized void setRoundingIncrement(double);
- method public synchronized void setRoundingMode(int);
method public synchronized void setScientificNotation(boolean);
method public synchronized void setSecondaryGroupingSize(int);
method public synchronized void setSignificantDigitsUsed(boolean);
@@ -22979,6 +23001,10 @@
field public static final java.lang.String KEY_DURATION = "durationUs";
field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
+ field public static final java.lang.String KEY_GRID_COLS = "grid-cols";
+ field public static final java.lang.String KEY_GRID_HEIGHT = "grid-height";
+ field public static final java.lang.String KEY_GRID_ROWS = "grid-rows";
+ field public static final java.lang.String KEY_GRID_WIDTH = "grid-width";
field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info";
field public static final java.lang.String KEY_HEIGHT = "height";
field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
@@ -23022,6 +23048,7 @@
field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw";
field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
+ field public static final java.lang.String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt";
field public static final java.lang.String MIMETYPE_VIDEO_AVC = "video/avc";
@@ -23114,9 +23141,13 @@
ctor public MediaMetadataRetriever();
method public java.lang.String extractMetadata(int);
method public byte[] getEmbeddedPicture();
+ method public android.graphics.Bitmap getFrameAtIndex(int);
method public android.graphics.Bitmap getFrameAtTime(long, int);
method public android.graphics.Bitmap getFrameAtTime(long);
method public android.graphics.Bitmap getFrameAtTime();
+ method public android.graphics.Bitmap[] getFramesAtIndex(int, int);
+ method public android.graphics.Bitmap getImageAtIndex(int);
+ method public android.graphics.Bitmap getPrimaryImage();
method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
method public void release();
method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
@@ -23139,11 +23170,18 @@
field public static final int METADATA_KEY_DURATION = 9; // 0x9
field public static final int METADATA_KEY_GENRE = 6; // 0x6
field public static final int METADATA_KEY_HAS_AUDIO = 16; // 0x10
+ field public static final int METADATA_KEY_HAS_IMAGE = 26; // 0x1a
field public static final int METADATA_KEY_HAS_VIDEO = 17; // 0x11
+ field public static final int METADATA_KEY_IMAGE_COUNT = 27; // 0x1b
+ field public static final int METADATA_KEY_IMAGE_HEIGHT = 30; // 0x1e
+ field public static final int METADATA_KEY_IMAGE_PRIMARY = 28; // 0x1c
+ field public static final int METADATA_KEY_IMAGE_ROTATION = 31; // 0x1f
+ field public static final int METADATA_KEY_IMAGE_WIDTH = 29; // 0x1d
field public static final int METADATA_KEY_LOCATION = 23; // 0x17
field public static final int METADATA_KEY_MIMETYPE = 12; // 0xc
field public static final int METADATA_KEY_NUM_TRACKS = 10; // 0xa
field public static final int METADATA_KEY_TITLE = 7; // 0x7
+ field public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32; // 0x20
field public static final int METADATA_KEY_VIDEO_HEIGHT = 19; // 0x13
field public static final int METADATA_KEY_VIDEO_ROTATION = 24; // 0x18
field public static final int METADATA_KEY_VIDEO_WIDTH = 18; // 0x12
@@ -25981,7 +26019,6 @@
public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
method public void close();
- method protected void finalize();
method public int getSpi();
}
@@ -32046,17 +32083,16 @@
public static final class StrictMode.ViolationInfo implements android.os.Parcelable {
ctor public StrictMode.ViolationInfo();
ctor public StrictMode.ViolationInfo(java.lang.Throwable, int);
- ctor public StrictMode.ViolationInfo(java.lang.String, java.lang.Throwable, int);
ctor public StrictMode.ViolationInfo(android.os.Parcel);
ctor public StrictMode.ViolationInfo(android.os.Parcel, boolean);
method public int describeContents();
method public void dump(android.util.Printer, java.lang.String);
+ method public java.lang.String getStackTrace();
+ method public java.lang.String getViolationDetails();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.os.StrictMode.ViolationInfo> CREATOR;
field public java.lang.String broadcastIntentAction;
- field public final android.app.ApplicationErrorReport.CrashInfo crashInfo;
field public int durationMillis;
- field public final java.lang.String message;
field public int numAnimationsRunning;
field public long numInstances;
field public final int policy;
@@ -32184,6 +32220,7 @@
field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
+ field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -37607,6 +37644,7 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+ field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2
field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
}
@@ -37614,6 +37652,7 @@
ctor public FillResponse.Builder();
method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
method public android.service.autofill.FillResponse build();
+ method public android.service.autofill.FillResponse.Builder disableAutofill(long);
method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
method public android.service.autofill.FillResponse.Builder setFlags(int);
@@ -40262,12 +40301,14 @@
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_MODE_INT = "cdma_roaming_mode_int";
field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
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_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ field public static final java.lang.String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
@@ -40281,6 +40322,7 @@
field public static final java.lang.String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = "duration_blocking_disabled_after_emergency_int";
field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
+ field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
@@ -40372,6 +40414,8 @@
method public int getLatitude();
method public int getLongitude();
method public int getNetworkId();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public int getSystemId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
@@ -40383,8 +40427,13 @@
method public int getBsic();
method public int getCid();
method public int getLac();
- method public int getMcc();
- method public int getMnc();
+ method public deprecated int getMcc();
+ method public java.lang.String getMccStr();
+ method public deprecated int getMnc();
+ method public java.lang.String getMncStr();
+ method public java.lang.String getMobileNetworkOperator();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public deprecated int getPsc();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
@@ -40394,8 +40443,13 @@
method public int describeContents();
method public int getCi();
method public int getEarfcn();
- method public int getMcc();
- method public int getMnc();
+ method public deprecated int getMcc();
+ method public java.lang.String getMccStr();
+ method public deprecated int getMnc();
+ method public java.lang.String getMncStr();
+ method public java.lang.String getMobileNetworkOperator();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public int getPci();
method public int getTac();
method public void writeToParcel(android.os.Parcel, int);
@@ -40406,8 +40460,13 @@
method public int describeContents();
method public int getCid();
method public int getLac();
- method public int getMcc();
- method public int getMnc();
+ method public deprecated int getMcc();
+ method public java.lang.String getMccStr();
+ method public deprecated int getMnc();
+ method public java.lang.String getMncStr();
+ method public java.lang.String getMobileNetworkOperator();
+ method public java.lang.CharSequence getOperatorAlphaLong();
+ method public java.lang.CharSequence getOperatorAlphaShort();
method public int getPsc();
method public int getUarfcn();
method public void writeToParcel(android.os.Parcel, int);
@@ -40559,6 +40618,7 @@
field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_REQUEST = "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_RESULT = "android.telephony.extra.MBMS_DOWNLOAD_RESULT";
field public static final java.lang.String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
+ field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA = "mbms-download-service-override";
field public static final int RESULT_CANCELLED = 2; // 0x2
field public static final int RESULT_DOWNLOAD_FAILURE = 6; // 0x6
field public static final int RESULT_EXPIRED = 3; // 0x3
@@ -40580,6 +40640,7 @@
method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, android.os.Handler);
method public void requestUpdateStreamingServices(java.util.List<java.lang.String>);
method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler);
+ field public static final java.lang.String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = "mbms-streaming-service-override";
}
public class NeighboringCellInfo implements android.os.Parcelable {
@@ -40840,6 +40901,8 @@
field public static final int ENCODING_7BIT = 1; // 0x1
field public static final int ENCODING_8BIT = 2; // 0x2
field public static final int ENCODING_UNKNOWN = 0; // 0x0
+ field public static final java.lang.String FORMAT_3GPP = "3gpp";
+ field public static final java.lang.String FORMAT_3GPP2 = "3gpp2";
field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c
field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86
field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0
@@ -40996,6 +41059,10 @@
field public static final int CALL_STATE_IDLE = 0; // 0x0
field public static final int CALL_STATE_OFFHOOK = 2; // 0x2
field public static final int CALL_STATE_RINGING = 1; // 0x1
+ field public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1
+ field public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2
+ field public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0
+ field public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1; // 0xffffffff
field public static final int DATA_ACTIVITY_DORMANT = 4; // 0x4
field public static final int DATA_ACTIVITY_IN = 1; // 0x1
field public static final int DATA_ACTIVITY_INOUT = 3; // 0x3
@@ -41259,6 +41326,7 @@
}
public final class FileServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable {
+ ctor public FileServiceInfo(java.util.Map<java.util.Locale, java.lang.String>, java.lang.String, java.util.List<java.util.Locale>, java.lang.String, java.util.Date, java.util.Date, java.util.List<android.telephony.mbms.FileInfo>);
method public int describeContents();
method public java.util.List<android.telephony.mbms.FileInfo> getFiles();
method public void writeToParcel(android.os.Parcel, int);
@@ -41357,6 +41425,7 @@
}
public final class StreamingServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable {
+ ctor public StreamingServiceInfo(java.util.Map<java.util.Locale, java.lang.String>, java.lang.String, java.util.List<java.util.Locale>, java.lang.String, java.util.Date, java.util.Date);
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.mbms.StreamingServiceInfo> CREATOR;
@@ -41364,6 +41433,37 @@
}
+package android.telephony.mbms.vendor {
+
+ public class MbmsDownloadServiceBase extends android.os.Binder {
+ ctor public MbmsDownloadServiceBase();
+ method public int cancelDownload(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException;
+ method public void dispose(int) throws android.os.RemoteException;
+ method public int download(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException;
+ method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo) throws android.os.RemoteException;
+ method public int initialize(int, android.telephony.mbms.MbmsDownloadSessionCallback) throws android.os.RemoteException;
+ method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads(int) throws android.os.RemoteException;
+ method public void onAppCallbackDied(int, int);
+ method public int registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback) throws android.os.RemoteException;
+ method public int requestUpdateFileServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException;
+ method public int resetDownloadKnowledge(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException;
+ method public int setTempFileRootDirectory(int, java.lang.String) throws android.os.RemoteException;
+ method public int unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback) throws android.os.RemoteException;
+ }
+
+ public class MbmsStreamingServiceBase extends android.os.Binder {
+ ctor public MbmsStreamingServiceBase();
+ method public void dispose(int) throws android.os.RemoteException;
+ method public android.net.Uri getPlaybackUri(int, java.lang.String) throws android.os.RemoteException;
+ method public int initialize(android.telephony.mbms.MbmsStreamingSessionCallback, int) throws android.os.RemoteException;
+ method public void onAppCallbackDied(int, int);
+ method public int requestUpdateStreamingServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException;
+ method public int startStreaming(int, java.lang.String, android.telephony.mbms.StreamingServiceCallback) throws android.os.RemoteException;
+ method public void stopStreaming(int, java.lang.String) throws android.os.RemoteException;
+ }
+
+}
+
package android.test {
public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
@@ -44881,25 +44981,25 @@
field public static final long FIELD_COUNT_UNKNOWN = 0L; // 0x0L
field public static final int FIELD_ID_MASK = -8; // 0xfffffff8
field public static final int FIELD_ID_SHIFT = 3; // 0x3
- field public static final long FIELD_TYPE_BOOL = 55834574848L; // 0xd00000000L
- field public static final long FIELD_TYPE_BYTES = 64424509440L; // 0xf00000000L
+ field public static final long FIELD_TYPE_BOOL = 34359738368L; // 0x800000000L
+ field public static final long FIELD_TYPE_BYTES = 51539607552L; // 0xc00000000L
field public static final long FIELD_TYPE_DOUBLE = 4294967296L; // 0x100000000L
- field public static final long FIELD_TYPE_ENUM = 68719476736L; // 0x1000000000L
- field public static final long FIELD_TYPE_FIXED32 = 38654705664L; // 0x900000000L
- field public static final long FIELD_TYPE_FIXED64 = 42949672960L; // 0xa00000000L
+ field public static final long FIELD_TYPE_ENUM = 60129542144L; // 0xe00000000L
+ field public static final long FIELD_TYPE_FIXED32 = 30064771072L; // 0x700000000L
+ field public static final long FIELD_TYPE_FIXED64 = 25769803776L; // 0x600000000L
field public static final long FIELD_TYPE_FLOAT = 8589934592L; // 0x200000000L
- field public static final long FIELD_TYPE_INT32 = 12884901888L; // 0x300000000L
- field public static final long FIELD_TYPE_INT64 = 17179869184L; // 0x400000000L
+ field public static final long FIELD_TYPE_INT32 = 21474836480L; // 0x500000000L
+ field public static final long FIELD_TYPE_INT64 = 12884901888L; // 0x300000000L
field public static final long FIELD_TYPE_MASK = 1095216660480L; // 0xff00000000L
- field public static final long FIELD_TYPE_OBJECT = 73014444032L; // 0x1100000000L
- field public static final long FIELD_TYPE_SFIXED32 = 47244640256L; // 0xb00000000L
- field public static final long FIELD_TYPE_SFIXED64 = 51539607552L; // 0xc00000000L
+ field public static final long FIELD_TYPE_MESSAGE = 47244640256L; // 0xb00000000L
+ field public static final long FIELD_TYPE_SFIXED32 = 64424509440L; // 0xf00000000L
+ field public static final long FIELD_TYPE_SFIXED64 = 68719476736L; // 0x1000000000L
field public static final int FIELD_TYPE_SHIFT = 32; // 0x20
- field public static final long FIELD_TYPE_SINT32 = 30064771072L; // 0x700000000L
- field public static final long FIELD_TYPE_SINT64 = 34359738368L; // 0x800000000L
- field public static final long FIELD_TYPE_STRING = 60129542144L; // 0xe00000000L
- field public static final long FIELD_TYPE_UINT32 = 21474836480L; // 0x500000000L
- field public static final long FIELD_TYPE_UINT64 = 25769803776L; // 0x600000000L
+ field public static final long FIELD_TYPE_SINT32 = 73014444032L; // 0x1100000000L
+ field public static final long FIELD_TYPE_SINT64 = 77309411328L; // 0x1200000000L
+ field public static final long FIELD_TYPE_STRING = 38654705664L; // 0x900000000L
+ field public static final long FIELD_TYPE_UINT32 = 55834574848L; // 0xd00000000L
+ field public static final long FIELD_TYPE_UINT64 = 17179869184L; // 0x400000000L
field public static final long FIELD_TYPE_UNKNOWN = 0L; // 0x0L
field public static final java.lang.String TAG = "ProtoOutputStream";
field public static final int WIRE_TYPE_END_GROUP = 4; // 0x4
@@ -52680,7 +52780,6 @@
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
- method public void invalidate(int, int, int, int);
method public boolean isAllCaps();
method public boolean isCursorVisible();
method public boolean isElegantTextHeight();
diff --git a/cmds/am/proto/instrumentation_data.proto b/cmds/am/proto/instrumentation_data.proto
index 12a18a2..8e29f96 100644
--- a/cmds/am/proto/instrumentation_data.proto
+++ b/cmds/am/proto/instrumentation_data.proto
@@ -28,6 +28,7 @@
optional double value_double = 5;
optional sint64 value_long = 6;
optional ResultsBundle value_bundle = 7;
+ optional bytes value_bytes = 8;
}
message ResultsBundle {
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index ab075ee..813335a 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -98,7 +98,8 @@
static final class MyShellCallback extends ShellCallback {
boolean mActive = true;
- @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
+ String mode) {
if (!mActive) {
System.err.println("Open attempt after active for: " + path);
return null;
@@ -159,7 +160,11 @@
} else if (opt.equals("-r")) {
instrument.rawMode = true;
} else if (opt.equals("-m")) {
- instrument.proto = true;
+ instrument.protoStd = true;
+ } else if (opt.equals("-f")) {
+ instrument.protoFile = true;
+ if (peekNextArg() != null && !peekNextArg().startsWith("-"))
+ instrument.logPath = nextArg();
} else if (opt.equals("-e")) {
final String argKey = nextArgRequired();
final String argValue = nextArgRequired();
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
index b69ef1c..93b9f58 100644
--- a/cmds/am/src/com/android/commands/am/Instrument.java
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -25,23 +25,32 @@
import android.content.pm.InstrumentationInfo;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.AndroidException;
import android.util.proto.ProtoOutputStream;
import android.view.IWindowManager;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
+import java.util.Locale;
/**
* Runs the am instrument command
*/
public class Instrument {
+ public static final String DEFAULT_LOG_DIR = "instrument-logs";
+
private final IActivityManager mAm;
private final IPackageManager mPm;
private final IWindowManager mWm;
@@ -50,7 +59,9 @@
public String profileFile = null;
public boolean wait = false;
public boolean rawMode = false;
- public boolean proto = false;
+ boolean protoStd = false; // write proto to stdout
+ boolean protoFile = false; // write proto to a file
+ String logPath = null;
public boolean noWindowAnimation = false;
public String abi = null;
public int userId = UserHandle.USER_CURRENT;
@@ -178,18 +189,49 @@
* Printer for the protobuf based status reporting.
*/
private class ProtoStatusReporter implements StatusReporter {
+
+ private File mLog;
+
+ ProtoStatusReporter() {
+ if (protoFile) {
+ if (logPath == null) {
+ File logDir = new File(Environment.getLegacyExternalStorageDirectory(),
+ DEFAULT_LOG_DIR);
+ if (!logDir.exists() && !logDir.mkdirs()) {
+ System.err.format("Unable to create log directory: %s\n",
+ logDir.getAbsolutePath());
+ protoFile = false;
+ return;
+ }
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-hhmmss-SSS", Locale.US);
+ String fileName = String.format("log-%s.instrumentation_data_proto",
+ format.format(new Date()));
+ mLog = new File(logDir, fileName);
+ } else {
+ mLog = new File(Environment.getLegacyExternalStorageDirectory(), logPath);
+ File logDir = mLog.getParentFile();
+ if (!logDir.exists() && !logDir.mkdirs()) {
+ System.err.format("Unable to create log directory: %s\n",
+ logDir.getAbsolutePath());
+ protoFile = false;
+ return;
+ }
+ }
+ if (mLog.exists()) mLog.delete();
+ }
+ }
+
@Override
public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
Bundle results) {
final ProtoOutputStream proto = new ProtoOutputStream();
- final long token = proto.startRepeatedObject(InstrumentationData.Session.TEST_STATUS);
-
- proto.writeSInt32(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
+ final long token = proto.start(InstrumentationData.Session.TEST_STATUS);
+ proto.write(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
+ proto.end(token);
- proto.endRepeatedObject(token);
- writeProtoToStdout(proto);
+ outputProto(proto);
}
@Override
@@ -197,80 +239,87 @@
Bundle results) {
final ProtoOutputStream proto = new ProtoOutputStream();
- final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
-
- proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+ final long token = proto.start(InstrumentationData.Session.SESSION_STATUS);
+ proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
InstrumentationData.SESSION_FINISHED);
- proto.writeSInt32(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
+ proto.write(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results);
+ proto.end(token);
- proto.endObject(token);
- writeProtoToStdout(proto);
+ outputProto(proto);
}
@Override
public void onError(String errorText, boolean commandError) {
final ProtoOutputStream proto = new ProtoOutputStream();
- final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
-
- proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+ final long token = proto.start(InstrumentationData.Session.SESSION_STATUS);
+ proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
InstrumentationData.SESSION_ABORTED);
- proto.writeString(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
+ proto.write(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
+ proto.end(token);
- proto.endObject(token);
- writeProtoToStdout(proto);
+ outputProto(proto);
}
private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
- final long bundleToken = proto.startObject(fieldId);
+ final long bundleToken = proto.start(fieldId);
for (final String key: sorted(bundle.keySet())) {
final long entryToken = proto.startRepeatedObject(
InstrumentationData.ResultsBundle.ENTRIES);
- proto.writeString(InstrumentationData.ResultsBundleEntry.KEY, key);
+ proto.write(InstrumentationData.ResultsBundleEntry.KEY, key);
final Object val = bundle.get(key);
if (val instanceof String) {
- proto.writeString(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
(String)val);
} else if (val instanceof Byte) {
- proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT,
((Byte)val).intValue());
} else if (val instanceof Double) {
- proto.writeDouble(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE,
- ((Double)val).doubleValue());
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE, (double)val);
} else if (val instanceof Float) {
- proto.writeFloat(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT,
- ((Float)val).floatValue());
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT, (float)val);
} else if (val instanceof Integer) {
- proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
- ((Integer)val).intValue());
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (int)val);
} else if (val instanceof Long) {
- proto.writeSInt64(InstrumentationData.ResultsBundleEntry.VALUE_LONG,
- ((Long)val).longValue());
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_LONG, (long)val);
} else if (val instanceof Short) {
- proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
- ((Short)val).intValue());
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (short)val);
} else if (val instanceof Bundle) {
writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE,
(Bundle)val);
+ } else if (val instanceof byte[]) {
+ proto.write(InstrumentationData.ResultsBundleEntry.VALUE_BYTES, (byte[])val);
}
- proto.endRepeatedObject(entryToken);
+ proto.end(entryToken);
}
- proto.endObject(bundleToken);
+ proto.end(bundleToken);
}
- private void writeProtoToStdout(ProtoOutputStream proto) {
- try {
- System.out.write(proto.getBytes());
- System.out.flush();
- } catch (IOException ex) {
- System.err.println("Error writing finished response: ");
- ex.printStackTrace(System.err);
+ private void outputProto(ProtoOutputStream proto) {
+ byte[] out = proto.getBytes();
+ if (protoStd) {
+ try {
+ System.out.write(out);
+ System.out.flush();
+ } catch (IOException ex) {
+ System.err.println("Error writing finished response: ");
+ ex.printStackTrace(System.err);
+ }
+ }
+ if (protoFile) {
+ try (OutputStream os = new FileOutputStream(mLog, true)) {
+ os.write(proto.getBytes());
+ os.flush();
+ } catch (IOException ex) {
+ System.err.format("Cannot write to %s:\n", mLog.getAbsolutePath());
+ ex.printStackTrace();
+ }
}
}
}
@@ -374,7 +423,7 @@
try {
// Choose which output we will do.
- if (proto) {
+ if (protoFile || protoStd) {
reporter = new ProtoStatusReporter();
} else if (wait) {
reporter = new TextStatusReporter(rawMode);
@@ -396,7 +445,7 @@
mWm.setAnimationScale(2, 0.0f);
}
- // Figure out which component we are tring to do.
+ // Figure out which component we are trying to do.
final ComponentName cn = parseComponentName(componentNameArg);
// Choose an ABI if necessary
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index 0532083..2ef0371 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -8,44 +8,53 @@
"-O0"
],
- srcs: [
- "IncidentHelper.cpp",
- "ih_util.cpp",
+ local_include_dirs: [
+ "src/",
+ "src/parsers/",
],
+ srcs: [
+ "src/parsers/*.cpp",
+ "src/TextParserBase.cpp",
+ "src/ih_util.cpp",
+ ],
+
+ generated_headers: ["gen-platform-proto-constants"],
+
shared_libs: [
"libbase",
"liblog",
- "libprotobuf-cpp-full",
+ "libprotoutil",
"libutils",
],
-
- static_libs: [
- "libplatformprotos",
- ],
}
cc_binary {
name: "incident_helper",
defaults: ["incident_helper_defaults"],
- srcs: ["main.cpp"],
+ srcs: ["src/main.cpp"],
}
cc_test {
name: "incident_helper_test",
defaults: ["incident_helper_defaults"],
+ local_include_dirs: ["src/"],
srcs: [
- "tests/IncidentHelper_test.cpp",
- "tests/ih_util_test.cpp",
+ "tests/*.cpp",
],
data: [
"testdata/*",
],
+ shared_libs: [
+ "libprotobuf-cpp-full",
+ ],
+
static_libs: [
"libgmock",
+ "libplatformprotos"
],
-}
\ No newline at end of file
+}
diff --git a/cmds/incident_helper/IncidentHelper.cpp b/cmds/incident_helper/IncidentHelper.cpp
deleted file mode 100644
index 7b06d42..0000000
--- a/cmds/incident_helper/IncidentHelper.cpp
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "incident_helper"
-
-#include "IncidentHelper.h"
-#include "ih_util.h"
-
-#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
-#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
-#include "frameworks/base/core/proto/android/os/procrank.pb.h"
-
-#include <android-base/file.h>
-#include <unistd.h>
-#include <string>
-#include <vector>
-
-using namespace android::base;
-using namespace android::os;
-using namespace google::protobuf;
-using namespace std;
-
-
-static const string TAB_DELIMITER = "\t";
-static const string COMMA_DELIMITER = ",";
-
-static inline int toInt(const string& s) {
- return atoi(s.c_str());
-}
-
-static inline long toLong(const string& s) {
- return atol(s.c_str());
-}
-
-/**
- * Sets the given protobuf message when the field name matches one of the
- * fields. It is useful to set values to proto from table-like plain texts.
- */
-static bool
-SetTableField(::google::protobuf::Message* message, string field_name, string field_value) {
- const Descriptor* descriptor = message->GetDescriptor();
- const Reflection* reflection = message->GetReflection();
-
- const FieldDescriptor* field = descriptor->FindFieldByName(field_name);
- switch (field->type()) {
- case FieldDescriptor::TYPE_STRING:
- reflection->SetString(message, field, field_value);
- return true;
- case FieldDescriptor::TYPE_INT64:
- reflection->SetInt64(message, field, toLong(field_value));
- return true;
- case FieldDescriptor::TYPE_UINT64:
- reflection->SetUInt64(message, field, toLong(field_value));
- return true;
- case FieldDescriptor::TYPE_INT32:
- reflection->SetInt32(message, field, toInt(field_value));
- return true;
- case FieldDescriptor::TYPE_UINT32:
- reflection->SetUInt32(message, field, toInt(field_value));
- return true;
- default:
- // Add new scalar types
- return false;
- }
-}
-
-// ================================================================================
-status_t NoopParser::Parse(const int in, const int out) const
-{
- string content;
- if (!ReadFdToString(in, &content)) {
- fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
- return -1;
- }
- if (!WriteStringToFd(content, out)) {
- fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
- return -1;
- }
- return NO_ERROR;
-}
-
-// ================================================================================
-status_t ReverseParser::Parse(const int in, const int out) const
-{
- string content;
- if (!ReadFdToString(in, &content)) {
- fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
- return -1;
- }
- // reverse the content
- reverse(content.begin(), content.end());
- if (!WriteStringToFd(content, out)) {
- fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
- return -1;
- }
- return NO_ERROR;
-}
-
-// ================================================================================
-status_t KernelWakesParser::Parse(const int in, const int out) const {
- Reader reader(in);
- string line;
- header_t header; // the header of /d/wakeup_sources
- record_t record; // retain each record
- int nline = 0;
-
- KernelWakeSources proto;
-
- // parse line by line
- while (reader.readLine(&line)) {
- if (line.empty()) continue;
- // parse head line
- if (nline++ == 0) {
- header = parseHeader(line, TAB_DELIMITER);
- continue;
- }
-
- // parse for each record, the line delimiter is \t only!
- record = parseRecord(line, TAB_DELIMITER);
-
- if (record.size() != header.size()) {
- // TODO: log this to incident report!
- fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
- continue;
- }
-
- WakeupSourceProto* source = proto.add_wakeup_sources();
- for (int i=0; i<(int)record.size(); i++) {
- if (!SetTableField(source, header[i], record[i])) {
- fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
- this->name.string(), nline, header[i].c_str(), record[i].c_str());
- }
- }
- }
-
- if (!reader.ok(&line)) {
- fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
- return -1;
- }
-
- if (!proto.SerializeToFileDescriptor(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
- return -1;
- }
- fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
- return NO_ERROR;
-}
-
-// ================================================================================
-status_t ProcrankParser::Parse(const int in, const int out) const {
- Reader reader(in);
- string line;
- header_t header; // the header of /d/wakeup_sources
- record_t record; // retain each record
- int nline = 0;
-
- Procrank proto;
-
- // parse line by line
- while (reader.readLine(&line)) {
- if (line.empty()) continue;
-
- // parse head line
- if (nline++ == 0) {
- header = parseHeader(line);
- continue;
- }
-
- if (hasPrefix(&line, "ZRAM:")) {
- proto.mutable_summary()->mutable_zram()->set_raw_text(line);
- continue;
- }
- if (hasPrefix(&line, "RAM:")) {
- proto.mutable_summary()->mutable_ram()->set_raw_text(line);
- continue;
- }
-
- record = parseRecord(line);
- if (record.size() != header.size()) {
- if (record[record.size() - 1] == "TOTAL") { // TOTAL record
- ProcessProto* total = proto.mutable_summary()->mutable_total();
- for (int i=1; i<=(int)record.size(); i++) {
- SetTableField(total, header[header.size() - i], record[record.size() - i]);
- }
- } else {
- fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
- line.c_str());
- }
- continue;
- }
-
- ProcessProto* process = proto.add_processes();
- for (int i=0; i<(int)record.size(); i++) {
- if (!SetTableField(process, header[i], record[i])) {
- fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
- this->name.string(), nline, header[i].c_str(), record[i].c_str());
- }
- }
- }
-
- if (!reader.ok(&line)) {
- fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
- return -1;
- }
-
- if (!proto.SerializeToFileDescriptor(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
- return -1;
- }
- fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
- return NO_ERROR;
-}
-
-// ================================================================================
-status_t PageTypeInfoParser::Parse(const int in, const int out) const {
- Reader reader(in);
- string line;
- bool migrateTypeSession = false;
- int pageBlockOrder;
- header_t blockHeader;
-
- PageTypeInfo pageTypeInfo;
-
- while (reader.readLine(&line)) {
- if (line.empty()) {
- migrateTypeSession = false;
- blockHeader.clear();
- continue;
- }
-
- if (hasPrefix(&line, "Page block order:")) {
- pageBlockOrder = toInt(line);
- pageTypeInfo.set_page_block_order(pageBlockOrder);
- continue;
- }
- if (hasPrefix(&line, "Pages per block:")) {
- pageTypeInfo.set_pages_per_block(toInt(line));
- continue;
- }
- if (hasPrefix(&line, "Free pages count per migrate type at order")) {
- migrateTypeSession = true;
- continue;
- }
- if (hasPrefix(&line, "Number of blocks type")) {
- blockHeader = parseHeader(line);
- continue;
- }
-
- record_t record = parseRecord(line, COMMA_DELIMITER);
- if (migrateTypeSession && record.size() == 3) {
- MigrateTypeProto* migrateType = pageTypeInfo.add_migrate_types();
- // expect part 0 starts with "Node"
- if (hasPrefix(&record[0], "Node")) {
- migrateType->set_node(toInt(record[0]));
- } else goto ERROR;
- // expect part 1 starts with "zone"
- if (hasPrefix(&record[1], "zone")) {
- migrateType->set_zone(record[1]);
- } else goto ERROR;
- // expect part 2 starts with "type"
- if (hasPrefix(&record[2], "type")) {
- // expect the rest of part 2 has number of (pageBlockOrder + 2) parts
- // An example looks like:
- // header line: type 0 1 2 3 4 5 6 7 8 9 10
- // record line: Unmovable 426 279 226 1 1 1 0 0 2 2 0
- // The pageBlockOrder = 10 and it's zero-indexed. so total parts
- // are 10 + 1(zero-indexed) + 1(the type part) = 12.
- record_t pageCounts = parseRecord(record[2]);
- int pageCountsSize = pageBlockOrder + 2;
- if ((int)pageCounts.size() != pageCountsSize) goto ERROR;
-
- migrateType->set_type(pageCounts[0]);
- for (auto i=1; i<pageCountsSize; i++) {
- migrateType->add_free_pages_count(toInt(pageCounts[i]));
- }
- } else goto ERROR;
- continue;
- }
-
- if (!blockHeader.empty() && record.size() == 2) {
- BlockProto* block = pageTypeInfo.add_blocks();
-
- if (hasPrefix(&record[0], "Node")) {
- block->set_node(toInt(record[0]));
- } else goto ERROR;
-
- if (hasPrefix(&record[1], "zone")) {
- record_t blockCounts = parseRecord(record[1]);
- block->set_zone(blockCounts[0]);
- for (size_t i=0; i<blockHeader.size(); i++) {
- if (!SetTableField(block, blockHeader[i], blockCounts[i+1])) goto ERROR;
- }
- } else goto ERROR;
-
- continue;
- }
-
-ERROR: // print out error for this single line and continue parsing
- fprintf(stderr, "[%s]Bad line: %s\n", this->name.string(), line.c_str());
- }
-
- if (!reader.ok(&line)) {
- fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
- return -1;
- }
-
- if (!pageTypeInfo.SerializeToFileDescriptor(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
- return -1;
- }
-
- fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), pageTypeInfo.ByteSize());
- return NO_ERROR;
-}
diff --git a/cmds/incident_helper/src/TextParserBase.cpp b/cmds/incident_helper/src/TextParserBase.cpp
new file mode 100644
index 0000000..a8f9968
--- /dev/null
+++ b/cmds/incident_helper/src/TextParserBase.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "incident_helper"
+
+#include "TextParserBase.h"
+
+#include <android-base/file.h>
+
+using namespace android::base;
+using namespace std;
+
+// ================================================================================
+status_t NoopParser::Parse(const int in, const int out) const
+{
+ string content;
+ if (!ReadFdToString(in, &content)) {
+ fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+ return -1;
+ }
+ if (!WriteStringToFd(content, out)) {
+ fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+ return -1;
+ }
+ return NO_ERROR;
+}
+
+// ================================================================================
+status_t ReverseParser::Parse(const int in, const int out) const
+{
+ string content;
+ if (!ReadFdToString(in, &content)) {
+ fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+ return -1;
+ }
+ // reverse the content
+ reverse(content.begin(), content.end());
+ if (!WriteStringToFd(content, out)) {
+ fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+ return -1;
+ }
+ return NO_ERROR;
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/IncidentHelper.h b/cmds/incident_helper/src/TextParserBase.h
similarity index 64%
rename from cmds/incident_helper/IncidentHelper.h
rename to cmds/incident_helper/src/TextParserBase.h
index d24d717..c41612d 100644
--- a/cmds/incident_helper/IncidentHelper.h
+++ b/cmds/incident_helper/src/TextParserBase.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef INCIDENT_HELPER_H
-#define INCIDENT_HELPER_H
+#ifndef TEXT_PARSER_BASE_H
+#define TEXT_PARSER_BASE_H
#include <utils/Errors.h>
#include <utils/String8.h>
@@ -68,37 +68,4 @@
virtual status_t Parse(const int in, const int out) const;
};
-/**
- * Kernel wakeup sources parser, parses text to protobuf in /d/wakeup_sources
- */
-class KernelWakesParser : public TextParserBase {
-public:
- KernelWakesParser() : TextParserBase(String8("KernelWakeSources")) {};
- ~KernelWakesParser() {};
-
- virtual status_t Parse(const int in, const int out) const;
-};
-
-/**
- * PageTypeInfo parser, parses text to protobuf in /proc/pageinfotype
- */
-class PageTypeInfoParser : public TextParserBase {
-public:
- PageTypeInfoParser() : TextParserBase(String8("PageTypeInfo")) {};
- ~PageTypeInfoParser() {};
-
- virtual status_t Parse(const int in, const int out) const;
-};
-
-/**
- * Procrank parser, parses text produced by command procrank
- */
-class ProcrankParser : public TextParserBase {
-public:
- ProcrankParser() : TextParserBase(String8("ProcrankParser")) {};
- ~ProcrankParser() {};
-
- virtual status_t Parse(const int in, const int out) const;
-};
-
-#endif // INCIDENT_HELPER_H
+#endif // TEXT_PARSER_BASE_H
\ No newline at end of file
diff --git a/cmds/incident_helper/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
similarity index 73%
rename from cmds/incident_helper/ih_util.cpp
rename to cmds/incident_helper/src/ih_util.cpp
index 2ab4b54..c7d1ca2 100644
--- a/cmds/incident_helper/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -87,6 +87,15 @@
return true;
}
+int toInt(const std::string& s) {
+ return atoi(s.c_str());
+}
+
+long long toLongLong(const std::string& s) {
+ return atoll(s.c_str());
+}
+
+// ==============================================================================
Reader::Reader(const int fd) : Reader(fd, BUFFER_SIZE) {};
Reader::Reader(const int fd, const size_t capacity)
@@ -151,3 +160,57 @@
error->assign(mStatus);
return mStatus.empty();
}
+
+// ==============================================================================
+Table::Table(const char* names[], const uint64_t ids[], const int count)
+ :mFieldNames(names),
+ mFieldIds(ids),
+ mFieldCount(count)
+{
+}
+
+Table::~Table()
+{
+}
+
+bool
+Table::insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value)
+{
+ uint64_t found = 0;
+ for (int i=0; i<mFieldCount; i++) {
+ if (strcmp(name.c_str(), mFieldNames[i]) == 0) {
+ found = mFieldIds[i];
+ break;
+ }
+ }
+
+ switch (found & FIELD_TYPE_MASK) {
+ case FIELD_TYPE_DOUBLE:
+ case FIELD_TYPE_FLOAT:
+ // TODO: support parse string to float/double
+ return false;
+ case FIELD_TYPE_STRING:
+ case FIELD_TYPE_BYTES:
+ proto.write(found, value);
+ break;
+ case FIELD_TYPE_INT64:
+ case FIELD_TYPE_SINT64:
+ case FIELD_TYPE_UINT64:
+ case FIELD_TYPE_FIXED64:
+ case FIELD_TYPE_SFIXED64:
+ proto.write(found, toLongLong(value));
+ break;
+ case FIELD_TYPE_BOOL:
+ case FIELD_TYPE_ENUM:
+ case FIELD_TYPE_INT32:
+ case FIELD_TYPE_SINT32:
+ case FIELD_TYPE_UINT32:
+ case FIELD_TYPE_FIXED32:
+ case FIELD_TYPE_SFIXED32:
+ proto.write(found, toInt(value));
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
diff --git a/cmds/incident_helper/ih_util.h b/cmds/incident_helper/src/ih_util.h
similarity index 79%
rename from cmds/incident_helper/ih_util.h
rename to cmds/incident_helper/src/ih_util.h
index ce5baee..86761e9 100644
--- a/cmds/incident_helper/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -21,6 +21,10 @@
#include <vector>
#include <sstream>
+#include <android/util/ProtoOutputStream.h>
+
+using namespace android::util;
+
typedef std::vector<std::string> header_t;
typedef std::vector<std::string> record_t;
typedef std::string (*trans_func) (const std::string&);
@@ -52,6 +56,12 @@
bool hasPrefix(std::string* line, const char* key);
/**
+ * Converts string to the desired type
+ */
+int toInt(const std::string& s);
+long long toLongLong(const std::string& s);
+
+/**
* Reader class reads data from given fd in streaming fashion.
* The buffer size is controlled by capacity parameter.
*/
@@ -78,4 +88,22 @@
inline bool EOR() { return mFd == -1 && mBufSize == 0; };
};
+/**
+ * The class contains a mapping between table headers to its field ids.
+ * And allow users to insert the field values to proto based on its header name.
+ */
+class Table
+{
+public:
+ Table(const char* names[], const uint64_t ids[], const int count);
+ ~Table();
+
+ bool insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value);
+
+private:
+ const char** mFieldNames;
+ const uint64_t* mFieldIds;
+ const int mFieldCount;
+};
+
#endif // INCIDENT_HELPER_UTIL_H
diff --git a/cmds/incident_helper/main.cpp b/cmds/incident_helper/src/main.cpp
similarity index 96%
rename from cmds/incident_helper/main.cpp
rename to cmds/incident_helper/src/main.cpp
index 52ff777..3da87b9c 100644
--- a/cmds/incident_helper/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,7 +16,9 @@
#define LOG_TAG "incident_helper"
-#include "IncidentHelper.h"
+#include "parsers/KernelWakesParser.h"
+#include "parsers/PageTypeInfoParser.h"
+#include "parsers/ProcrankParser.h"
#include <android-base/file.h>
#include <getopt.h>
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
new file mode 100644
index 0000000..cc4a1e1
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/kernelwake.proto.h"
+#include "ih_util.h"
+#include "KernelWakesParser.h"
+
+using namespace android::os;
+
+const std::string LINE_DELIMITER = "\t";
+
+status_t
+KernelWakesParser::Parse(const int in, const int out) const
+{
+ Reader reader(in);
+ string line;
+ header_t header; // the header of /d/wakeup_sources
+ record_t record; // retain each record
+ int nline = 0;
+
+ ProtoOutputStream proto;
+ Table table(WakeupSourceProto::_FIELD_NAMES, WakeupSourceProto::_FIELD_IDS, WakeupSourceProto::_FIELD_COUNT);
+
+ // parse line by line
+ while (reader.readLine(&line)) {
+ if (line.empty()) continue;
+ // parse head line
+ if (nline++ == 0) {
+ header = parseHeader(line, LINE_DELIMITER);
+ continue;
+ }
+
+ // parse for each record, the line delimiter is \t only!
+ record = parseRecord(line, LINE_DELIMITER);
+
+ if (record.size() != header.size()) {
+ // TODO: log this to incident report!
+ fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
+ continue;
+ }
+
+ long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES);
+ for (int i=0; i<(int)record.size(); i++) {
+ if (!table.insertField(proto, header[i], record[i])) {
+ fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
+ this->name.string(), nline, header[i].c_str(), record[i].c_str());
+ }
+ }
+ proto.end(token);
+ }
+
+ if (!reader.ok(&line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
+
+ if (!proto.flush(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.h b/cmds/incident_helper/src/parsers/KernelWakesParser.h
new file mode 100644
index 0000000..aabab7c
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KERNEL_WAKES_PARSER_H
+#define KERNEL_WAKES_PARSER_H
+
+#include "TextParserBase.h"
+
+/**
+ * Kernel wakeup sources parser, parses text to protobuf in /d/wakeup_sources
+ */
+class KernelWakesParser : public TextParserBase {
+public:
+ KernelWakesParser() : TextParserBase(String8("KernelWakeSources")) {};
+ ~KernelWakesParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif // KERNEL_WAKES_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
new file mode 100644
index 0000000..6047bd1
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/pagetypeinfo.proto.h"
+#include "ih_util.h"
+#include "PageTypeInfoParser.h"
+
+using namespace android::os;
+
+const std::string LINE_DELIMITER = ",";
+
+status_t
+PageTypeInfoParser::Parse(const int in, const int out) const
+{
+ Reader reader(in);
+ string line;
+ bool migrateTypeSession = false;
+ int pageBlockOrder;
+ header_t blockHeader;
+
+ ProtoOutputStream proto;
+ Table table(BlockProto::_FIELD_NAMES, BlockProto::_FIELD_IDS, BlockProto::_FIELD_COUNT);
+
+ while (reader.readLine(&line)) {
+ if (line.empty()) {
+ migrateTypeSession = false;
+ blockHeader.clear();
+ continue;
+ }
+
+ if (hasPrefix(&line, "Page block order:")) {
+ pageBlockOrder = toInt(line);
+ proto.write(PageTypeInfo::PAGE_BLOCK_ORDER, pageBlockOrder);
+ continue;
+ }
+ if (hasPrefix(&line, "Pages per block:")) {
+ proto.write(PageTypeInfo::PAGES_PER_BLOCK, toInt(line));
+ continue;
+ }
+ if (hasPrefix(&line, "Free pages count per migrate type at order")) {
+ migrateTypeSession = true;
+ continue;
+ }
+ if (hasPrefix(&line, "Number of blocks type")) {
+ blockHeader = parseHeader(line);
+ continue;
+ }
+
+ record_t record = parseRecord(line, LINE_DELIMITER);
+ if (migrateTypeSession && record.size() == 3) {
+ long long token = proto.start(PageTypeInfo::MIGRATE_TYPES);
+ // expect part 0 starts with "Node"
+ if (hasPrefix(&record[0], "Node")) {
+ proto.write(MigrateTypeProto::NODE, toInt(record[0]));
+ } else return BAD_VALUE;
+ // expect part 1 starts with "zone"
+ if (hasPrefix(&record[1], "zone")) {
+ proto.write(MigrateTypeProto::ZONE, record[1]);
+ } else return BAD_VALUE;
+ // expect part 2 starts with "type"
+ if (hasPrefix(&record[2], "type")) {
+ // expect the rest of part 2 has number of (pageBlockOrder + 2) parts
+ // An example looks like:
+ // header line: type 0 1 2 3 4 5 6 7 8 9 10
+ // record line: Unmovable 426 279 226 1 1 1 0 0 2 2 0
+ // The pageBlockOrder = 10 and it's zero-indexed. so total parts
+ // are 10 + 1(zero-indexed) + 1(the type part) = 12.
+ record_t pageCounts = parseRecord(record[2]);
+ int pageCountsSize = pageBlockOrder + 2;
+ if ((int)pageCounts.size() != pageCountsSize) return BAD_VALUE;
+
+ proto.write(MigrateTypeProto::TYPE, pageCounts[0]);
+ for (auto i=1; i<pageCountsSize; i++) {
+ proto.write(MigrateTypeProto::FREE_PAGES_COUNT, toInt(pageCounts[i]));
+ }
+ } else return BAD_VALUE;
+
+ proto.end(token);
+ } else if (!blockHeader.empty() && record.size() == 2) {
+ long long token = proto.start(PageTypeInfo::BLOCKS);
+ if (hasPrefix(&record[0], "Node")) {
+ proto.write(BlockProto::NODE, toInt(record[0]));
+ } else return BAD_VALUE;
+
+ if (hasPrefix(&record[1], "zone")) {
+ record_t blockCounts = parseRecord(record[1]);
+ proto.write(BlockProto::ZONE, blockCounts[0]);
+
+ for (size_t i=0; i<blockHeader.size(); i++) {
+ if (!table.insertField(proto, blockHeader[i], blockCounts[i+1])) {
+ return BAD_VALUE;
+ }
+ }
+ } else return BAD_VALUE;
+ proto.end(token);
+ }
+ }
+
+ if (!reader.ok(&line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
+
+ if (!proto.flush(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ return NO_ERROR;
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.h b/cmds/incident_helper/src/parsers/PageTypeInfoParser.h
new file mode 100644
index 0000000..fb84d91
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PAGE_TYPE_INFO_PARSER_H
+#define PAGE_TYPE_INFO_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * PageTypeInfo parser, parses text to protobuf in /proc/pageinfotype
+ */
+class PageTypeInfoParser : public TextParserBase {
+public:
+ PageTypeInfoParser() : TextParserBase(String8("PageTypeInfo")) {};
+ ~PageTypeInfoParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif // PAGE_TYPE_INFO_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.cpp b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
new file mode 100644
index 0000000..93f970f
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/procrank.proto.h"
+#include "ih_util.h"
+#include "ProcrankParser.h"
+
+using namespace android::os;
+
+status_t
+ProcrankParser::Parse(const int in, const int out) const
+{
+ Reader reader(in);
+ string line;
+ header_t header; // the header of /d/wakeup_sources
+ record_t record; // retain each record
+ int nline = 0;
+
+ ProtoOutputStream proto;
+ Table table(ProcessProto::_FIELD_NAMES, ProcessProto::_FIELD_IDS, ProcessProto::_FIELD_COUNT);
+ string zram, ram, total;
+
+ // parse line by line
+ while (reader.readLine(&line)) {
+ if (line.empty()) continue;
+
+ // parse head line
+ if (nline++ == 0) {
+ header = parseHeader(line);
+ continue;
+ }
+
+ if (hasPrefix(&line, "ZRAM:")) {
+ zram = line;
+ continue;
+ }
+ if (hasPrefix(&line, "RAM:")) {
+ ram = line;
+ continue;
+ }
+
+ record = parseRecord(line);
+ if (record.size() != header.size()) {
+ if (record[record.size() - 1] == "TOTAL") { // TOTAL record
+ total = line;
+ } else {
+ fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
+ line.c_str());
+ }
+ continue;
+ }
+
+ long long token = proto.start(Procrank::PROCESSES);
+ for (int i=0; i<(int)record.size(); i++) {
+ if (!table.insertField(proto, header[i], record[i])) {
+ fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
+ this->name.string(), nline, header[i].c_str(), record[i].c_str());
+ }
+ }
+ proto.end(token);
+ }
+
+ // add summary
+ long long token = proto.start(Procrank::SUMMARY);
+ if (!total.empty()) {
+ record = parseRecord(total);
+ long long token = proto.start(SummaryProto::TOTAL);
+ for (int i=(int)record.size(); i>0; i--) {
+ table.insertField(proto, header[header.size() - i].c_str(), record[record.size() - i].c_str());
+ }
+ proto.end(token);
+ }
+ if (!zram.empty()) {
+ long long token = proto.start(SummaryProto::ZRAM);
+ proto.write(ZramProto::RAW_TEXT, zram);
+ proto.end(token);
+ }
+ if (!ram.empty()) {
+ long long token = proto.start(SummaryProto::RAM);
+ proto.write(RamProto::RAW_TEXT, ram);
+ proto.end(token);
+ }
+ proto.end(token);
+
+ if (!reader.ok(&line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
+
+ if (!proto.flush(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.h b/cmds/incident_helper/src/parsers/ProcrankParser.h
new file mode 100644
index 0000000..5d0ee48
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PROCRANK_PARSER_H
+#define PROCRANK_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Procrank parser, parses text produced by command procrank
+ */
+class ProcrankParser : public TextParserBase {
+public:
+ ProcrankParser() : TextParserBase(String8("ProcrankParser")) {};
+ ~ProcrankParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif // PROCRANK_PARSER_H
diff --git a/cmds/incident_helper/testdata/kernel_wakeups_short.txt b/cmds/incident_helper/testdata/kernel_wakeups_short.txt
new file mode 100644
index 0000000..a51926e
--- /dev/null
+++ b/cmds/incident_helper/testdata/kernel_wakeups_short.txt
@@ -0,0 +1,3 @@
+name active_count last_change
+ab 8 123456123456
+df 143 0
diff --git a/cmds/incident_helper/tests/IncidentHelper_test.cpp b/cmds/incident_helper/tests/IncidentHelper_test.cpp
deleted file mode 100644
index c44a163..0000000
--- a/cmds/incident_helper/tests/IncidentHelper_test.cpp
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "IncidentHelper.h"
-
-#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
-#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
-#include "frameworks/base/core/proto/android/os/procrank.pb.h"
-
-#include <android-base/file.h>
-#include <android-base/test_utils.h>
-#include <gmock/gmock.h>
-#include <google/protobuf/message.h>
-#include <gtest/gtest.h>
-#include <string.h>
-#include <fcntl.h>
-
-using namespace android::base;
-using namespace android::os;
-using namespace std;
-using ::testing::StrEq;
-using ::testing::Test;
-using ::testing::internal::CaptureStderr;
-using ::testing::internal::CaptureStdout;
-using ::testing::internal::GetCapturedStderr;
-using ::testing::internal::GetCapturedStdout;
-
-class IncidentHelperTest : public Test {
-public:
- virtual void SetUp() override {
- ASSERT_TRUE(tf.fd != -1);
- }
-
- string getSerializedString(::google::protobuf::Message& message) {
- string expectedStr;
- message.SerializeToFileDescriptor(tf.fd);
- ReadFileToString(tf.path, &expectedStr);
- return expectedStr;
- }
-
-protected:
- TemporaryFile tf;
-
- const string kTestPath = GetExecutableDirectory();
- const string kTestDataPath = kTestPath + "/testdata/";
-};
-
-TEST_F(IncidentHelperTest, ReverseParser) {
- ReverseParser parser;
- TemporaryFile tf;
-
- ASSERT_TRUE(tf.fd != -1);
- ASSERT_TRUE(WriteStringToFile("TestData", tf.path, false));
-
- CaptureStdout();
- ASSERT_EQ(NO_ERROR, parser.Parse(tf.fd, STDOUT_FILENO));
- EXPECT_THAT(GetCapturedStdout(), StrEq("ataDtseT"));
-}
-
-TEST_F(IncidentHelperTest, KernelWakesParser) {
- const string testFile = kTestDataPath + "kernel_wakeups.txt";
- KernelWakesParser parser;
- KernelWakeSources expected;
-
- WakeupSourceProto* record1 = expected.add_wakeup_sources();
- record1->set_name("ipc000000ab_ATFWD-daemon");
- record1->set_active_count(8);
- record1->set_event_count(8);
- record1->set_wakeup_count(0);
- record1->set_expire_count(0);
- record1->set_active_since(0l);
- record1->set_total_time(0l);
- record1->set_max_time(0l);
- record1->set_last_change(131348l);
- record1->set_prevent_suspend_time(0l);
-
- WakeupSourceProto* record2 = expected.add_wakeup_sources();
- record2->set_name("ipc000000aa_ATFWD-daemon");
- record2->set_active_count(143);
- record2->set_event_count(143);
- record2->set_wakeup_count(0);
- record2->set_expire_count(0);
- record2->set_active_since(0l);
- record2->set_total_time(123l);
- record2->set_max_time(3l);
- record2->set_last_change(2067286206l);
- record2->set_prevent_suspend_time(0l);
-
- int fd = open(testFile.c_str(), O_RDONLY);
- ASSERT_TRUE(fd != -1);
-
- CaptureStdout();
- ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
- EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
- close(fd);
-}
-
-TEST_F(IncidentHelperTest, ProcrankParser) {
- const string testFile = kTestDataPath + "procrank.txt";
- ProcrankParser parser;
- Procrank expected;
-
- ProcessProto* process1 = expected.add_processes();
- process1->set_pid(1119);
- process1->set_vss(2607640);
- process1->set_rss(339564);
- process1->set_pss(180278);
- process1->set_uss(114216);
- process1->set_swap(1584);
- process1->set_pswap(46);
- process1->set_uswap(0);
- process1->set_zswap(10);
- process1->set_cmdline("system_server");
-
- ProcessProto* process2 = expected.add_processes();
- process2->set_pid(649);
- process2->set_vss(11016);
- process2->set_rss(1448);
- process2->set_pss(98);
- process2->set_uss(48);
- process2->set_swap(472);
- process2->set_pswap(342);
- process2->set_uswap(212);
- process2->set_zswap(75);
- process2->set_cmdline("/vendor/bin/qseecomd");
-
- ProcessProto* total = expected.mutable_summary()->mutable_total();
- total->set_pss(1201993);
- total->set_uss(935300);
- total->set_swap(88164);
- total->set_pswap(31069);
- total->set_uswap(27612);
- total->set_zswap(6826);
- total->set_cmdline("TOTAL");
-
- expected.mutable_summary()->mutable_zram()
- ->set_raw_text("6828K physical used for 31076K in swap (524284K total swap)");
- expected.mutable_summary()->mutable_ram()
- ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
-
- int fd = open(testFile.c_str(), O_RDONLY);
- ASSERT_TRUE(fd != -1);
-
- CaptureStdout();
- ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
- EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
- close(fd);
-}
-
-TEST_F(IncidentHelperTest, ProcrankParserShortHeader) {
- const string testFile = kTestDataPath + "procrank_short.txt";
- ProcrankParser parser;
- Procrank expected;
-
- ProcessProto* process1 = expected.add_processes();
- process1->set_pid(1119);
- process1->set_vss(2607640);
- process1->set_rss(339564);
- process1->set_pss(180278);
- process1->set_uss(114216);
- process1->set_cmdline("system_server");
-
- ProcessProto* process2 = expected.add_processes();
- process2->set_pid(649);
- process2->set_vss(11016);
- process2->set_rss(1448);
- process2->set_pss(98);
- process2->set_uss(48);
- process2->set_cmdline("/vendor/bin/qseecomd");
-
- ProcessProto* total = expected.mutable_summary()->mutable_total();
- total->set_pss(1201993);
- total->set_uss(935300);
- total->set_cmdline("TOTAL");
-
- expected.mutable_summary()->mutable_ram()
- ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
-
- int fd = open(testFile.c_str(), O_RDONLY);
- ASSERT_TRUE(fd != -1);
-
- CaptureStdout();
- ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
- EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
- close(fd);
-}
-
-TEST_F(IncidentHelperTest, PageTypeInfoParser) {
- const string testFile = kTestDataPath + "pagetypeinfo.txt";
- PageTypeInfoParser parser;
- PageTypeInfo expected;
-
- expected.set_page_block_order(10);
- expected.set_pages_per_block(1024);
-
- MigrateTypeProto* mt1 = expected.add_migrate_types();
- mt1->set_node(0);
- mt1->set_zone("DMA");
- mt1->set_type("Unmovable");
- int arr1[] = { 426, 279, 226, 1, 1, 1, 0, 0, 2, 2, 0};
- for (auto i=0; i<11; i++) {
- mt1->add_free_pages_count(arr1[i]);
- }
-
- MigrateTypeProto* mt2 = expected.add_migrate_types();
- mt2->set_node(0);
- mt2->set_zone("Normal");
- mt2->set_type("Reclaimable");
- int arr2[] = { 953, 773, 437, 154, 92, 26, 15, 14, 12, 7, 0};
- for (auto i=0; i<11; i++) {
- mt2->add_free_pages_count(arr2[i]);
- }
-
- BlockProto* block1 = expected.add_blocks();
- block1->set_node(0);
- block1->set_zone("DMA");
- block1->set_unmovable(74);
- block1->set_reclaimable(9);
- block1->set_movable(337);
- block1->set_cma(41);
- block1->set_reserve(1);
- block1->set_isolate(0);
-
-
- BlockProto* block2 = expected.add_blocks();
- block2->set_node(0);
- block2->set_zone("Normal");
- block2->set_unmovable(70);
- block2->set_reclaimable(12);
- block2->set_movable(423);
- block2->set_cma(0);
- block2->set_reserve(1);
- block2->set_isolate(0);
-
- int fd = open(testFile.c_str(), O_RDONLY);
- ASSERT_TRUE(fd != -1);
-
- CaptureStdout();
- ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
- EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
- close(fd);
-}
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/KernelWakesParser_test.cpp b/cmds/incident_helper/tests/KernelWakesParser_test.cpp
new file mode 100644
index 0000000..a8fa6208
--- /dev/null
+++ b/cmds/incident_helper/tests/KernelWakesParser_test.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "KernelWakesParser.h"
+
+#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class KernelWakesParserTest : public Test {
+public:
+ virtual void SetUp() override {
+ ASSERT_TRUE(tf.fd != -1);
+ }
+
+ string getSerializedString(::google::protobuf::Message& message) {
+ string expectedStr;
+ message.SerializeToFileDescriptor(tf.fd);
+ ReadFileToString(tf.path, &expectedStr);
+ return expectedStr;
+ }
+
+protected:
+ TemporaryFile tf;
+
+ const string kTestPath = GetExecutableDirectory();
+ const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(KernelWakesParserTest, Short) {
+ const string testFile = kTestDataPath + "kernel_wakeups_short.txt";
+ KernelWakesParser parser;
+ KernelWakeSources expected;
+
+ WakeupSourceProto* record1 = expected.add_wakeup_sources();
+ record1->set_name("ab");
+ record1->set_active_count(8);
+ record1->set_last_change(123456123456LL);
+
+ WakeupSourceProto* record2 = expected.add_wakeup_sources();
+ record2->set_name("df");
+ record2->set_active_count(143);
+ record2->set_last_change(0LL);
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+ close(fd);
+}
+
+TEST_F(KernelWakesParserTest, Normal) {
+ const string testFile = kTestDataPath + "kernel_wakeups.txt";
+ KernelWakesParser parser;
+ KernelWakeSources expected;
+
+ WakeupSourceProto* record1 = expected.add_wakeup_sources();
+ record1->set_name("ipc000000ab_ATFWD-daemon");
+ record1->set_active_count(8);
+ record1->set_event_count(8);
+ record1->set_wakeup_count(0);
+ record1->set_expire_count(0);
+ record1->set_active_since(0l);
+ record1->set_total_time(0l);
+ record1->set_max_time(0l);
+ record1->set_last_change(131348LL);
+ record1->set_prevent_suspend_time(0LL);
+
+ WakeupSourceProto* record2 = expected.add_wakeup_sources();
+ record2->set_name("ipc000000aa_ATFWD-daemon");
+ record2->set_active_count(143);
+ record2->set_event_count(143);
+ record2->set_wakeup_count(0);
+ record2->set_expire_count(0);
+ record2->set_active_since(0l);
+ record2->set_total_time(123l);
+ record2->set_max_time(3l);
+ record2->set_last_change(2067286206LL);
+ record2->set_prevent_suspend_time(0LL);
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+ close(fd);
+}
diff --git a/cmds/incident_helper/tests/PageTypeInfoParser_test.cpp b/cmds/incident_helper/tests/PageTypeInfoParser_test.cpp
new file mode 100644
index 0000000..de64e70
--- /dev/null
+++ b/cmds/incident_helper/tests/PageTypeInfoParser_test.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PageTypeInfoParser.h"
+
+#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class PageTypeInfoParserTest : public Test {
+public:
+ virtual void SetUp() override {
+ ASSERT_TRUE(tf.fd != -1);
+ }
+
+ string getSerializedString(::google::protobuf::Message& message) {
+ string expectedStr;
+ message.SerializeToFileDescriptor(tf.fd);
+ ReadFileToString(tf.path, &expectedStr);
+ return expectedStr;
+ }
+
+protected:
+ TemporaryFile tf;
+
+ const string kTestPath = GetExecutableDirectory();
+ const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(PageTypeInfoParserTest, Success) {
+ const string testFile = kTestDataPath + "pagetypeinfo.txt";
+ PageTypeInfoParser parser;
+ PageTypeInfo expected;
+
+ expected.set_page_block_order(10);
+ expected.set_pages_per_block(1024);
+
+ MigrateTypeProto* mt1 = expected.add_migrate_types();
+ mt1->set_node(0);
+ mt1->set_zone("DMA");
+ mt1->set_type("Unmovable");
+ int arr1[] = { 426, 279, 226, 1, 1, 1, 0, 0, 2, 2, 0};
+ for (auto i=0; i<11; i++) {
+ mt1->add_free_pages_count(arr1[i]);
+ }
+
+ MigrateTypeProto* mt2 = expected.add_migrate_types();
+ mt2->set_node(0);
+ mt2->set_zone("Normal");
+ mt2->set_type("Reclaimable");
+ int arr2[] = { 953, 773, 437, 154, 92, 26, 15, 14, 12, 7, 0};
+ for (auto i=0; i<11; i++) {
+ mt2->add_free_pages_count(arr2[i]);
+ }
+
+ BlockProto* block1 = expected.add_blocks();
+ block1->set_node(0);
+ block1->set_zone("DMA");
+ block1->set_unmovable(74);
+ block1->set_reclaimable(9);
+ block1->set_movable(337);
+ block1->set_cma(41);
+ block1->set_reserve(1);
+ block1->set_isolate(0);
+
+
+ BlockProto* block2 = expected.add_blocks();
+ block2->set_node(0);
+ block2->set_zone("Normal");
+ block2->set_unmovable(70);
+ block2->set_reclaimable(12);
+ block2->set_movable(423);
+ block2->set_cma(0);
+ block2->set_reserve(1);
+ block2->set_isolate(0);
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+ close(fd);
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/ProcrankParser_test.cpp b/cmds/incident_helper/tests/ProcrankParser_test.cpp
new file mode 100644
index 0000000..e86647a
--- /dev/null
+++ b/cmds/incident_helper/tests/ProcrankParser_test.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ProcrankParser.h"
+
+#include "frameworks/base/core/proto/android/os/procrank.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class ProcrankParserTest : public Test {
+public:
+ virtual void SetUp() override {
+ ASSERT_TRUE(tf.fd != -1);
+ }
+
+ string getSerializedString(::google::protobuf::Message& message) {
+ string expectedStr;
+ message.SerializeToFileDescriptor(tf.fd);
+ ReadFileToString(tf.path, &expectedStr);
+ return expectedStr;
+ }
+
+protected:
+ TemporaryFile tf;
+
+ const string kTestPath = GetExecutableDirectory();
+ const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(ProcrankParserTest, HasSwapInfo) {
+ const string testFile = kTestDataPath + "procrank.txt";
+ ProcrankParser parser;
+ Procrank expected;
+
+ ProcessProto* process1 = expected.add_processes();
+ process1->set_pid(1119);
+ process1->set_vss(2607640);
+ process1->set_rss(339564);
+ process1->set_pss(180278);
+ process1->set_uss(114216);
+ process1->set_swap(1584);
+ process1->set_pswap(46);
+ process1->set_uswap(0);
+ process1->set_zswap(10);
+ process1->set_cmdline("system_server");
+
+ ProcessProto* process2 = expected.add_processes();
+ process2->set_pid(649);
+ process2->set_vss(11016);
+ process2->set_rss(1448);
+ process2->set_pss(98);
+ process2->set_uss(48);
+ process2->set_swap(472);
+ process2->set_pswap(342);
+ process2->set_uswap(212);
+ process2->set_zswap(75);
+ process2->set_cmdline("/vendor/bin/qseecomd");
+
+ ProcessProto* total = expected.mutable_summary()->mutable_total();
+ total->set_pss(1201993);
+ total->set_uss(935300);
+ total->set_swap(88164);
+ total->set_pswap(31069);
+ total->set_uswap(27612);
+ total->set_zswap(6826);
+ total->set_cmdline("TOTAL");
+
+ expected.mutable_summary()->mutable_zram()
+ ->set_raw_text("6828K physical used for 31076K in swap (524284K total swap)");
+ expected.mutable_summary()->mutable_ram()
+ ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+ close(fd);
+}
+
+TEST_F(ProcrankParserTest, NoSwapInfo) {
+ const string testFile = kTestDataPath + "procrank_short.txt";
+ ProcrankParser parser;
+ Procrank expected;
+
+ ProcessProto* process1 = expected.add_processes();
+ process1->set_pid(1119);
+ process1->set_vss(2607640);
+ process1->set_rss(339564);
+ process1->set_pss(180278);
+ process1->set_uss(114216);
+ process1->set_cmdline("system_server");
+
+ ProcessProto* process2 = expected.add_processes();
+ process2->set_pid(649);
+ process2->set_vss(11016);
+ process2->set_rss(1448);
+ process2->set_pss(98);
+ process2->set_uss(48);
+ process2->set_cmdline("/vendor/bin/qseecomd");
+
+ ProcessProto* total = expected.mutable_summary()->mutable_total();
+ total->set_pss(1201993);
+ total->set_uss(935300);
+ total->set_cmdline("TOTAL");
+
+ expected.mutable_summary()->mutable_ram()
+ ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+ close(fd);
+}
diff --git a/cmds/incidentd/src/PrivacyBuffer.cpp b/cmds/incidentd/src/PrivacyBuffer.cpp
index d926ea7..77ae1a7 100644
--- a/cmds/incidentd/src/PrivacyBuffer.cpp
+++ b/cmds/incidentd/src/PrivacyBuffer.cpp
@@ -87,12 +87,12 @@
// current field is message type and its sub-fields have extra privacy policies
uint32_t msgSize = mData.readRawVarint();
EncodedBuffer::Pointer start = mData.rp()->copy();
+ long long token = mProto.start(policy->EncodedFieldId());
while (mData.rp()->pos() - start.pos() != msgSize) {
- long long token = mProto.start(policy->EncodedFieldId());
status_t err = stripField(policy, spec);
if (err != NO_ERROR) return err;
- mProto.end(token);
}
+ mProto.end(token);
return NO_ERROR;
}
diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h
index da82b00..dfd2312 100644
--- a/cmds/incidentd/src/section_list.h
+++ b/cmds/incidentd/src/section_list.h
@@ -30,7 +30,7 @@
* This is the mapping of section IDs to each section's privacy policy.
* The section IDs are guaranteed in ascending order, not NULL-terminated since size is provided.
*/
-extern const Privacy* PRIVACY_POLICY_LIST[];
+extern const Privacy** PRIVACY_POLICY_LIST;
extern const int PRIVACY_POLICY_COUNT;
diff --git a/cmds/incidentd/tests/PrivacyBuffer_test.cpp b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
index 8f6e355..84a2a82 100644
--- a/cmds/incidentd/tests/PrivacyBuffer_test.cpp
+++ b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
@@ -260,3 +260,13 @@
PrivacySpec spec;
ASSERT_EQ(privacyBuf.strip(spec), BAD_VALUE);
}
+
+TEST_F(PrivacyBufferTest, SelfRecursionMessage) {
+ string input = "\x2a\"" + VARINT_FIELD_1 + STRING_FIELD_2 + MESSAGE_FIELD_5;
+ writeToFdBuffer(input);
+ Privacy* field5 = create_message_privacy(5, NULL);
+ Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), field5, NULL };
+ field5->children = list;
+ string expected = "\x2a\x1c" + STRING_FIELD_2 + "\x2a\xd" + STRING_FIELD_2;
+ assertStrip(EXPLICIT, expected, field5);
+}
diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp
index 5d074bc..8d99fc7 100644
--- a/cmds/incidentd/tests/Reporter_test.cpp
+++ b/cmds/incidentd/tests/Reporter_test.cpp
@@ -32,8 +32,6 @@
using namespace std;
using ::testing::StrEq;
using ::testing::Test;
-using ::testing::internal::CaptureStdout;
-using ::testing::internal::GetCapturedStdout;
class TestListener : public IIncidentReportStatusListener
{
@@ -139,20 +137,24 @@
}
TEST_F(ReporterTest, RunReportWithHeaders) {
+ TemporaryFile tf;
IncidentReportArgs args1, args2;
args1.addSection(1);
args2.addSection(2);
std::vector<int8_t> header {'a', 'b', 'c', 'd', 'e'};
args2.addHeader(header);
- sp<ReportRequest> r1 = new ReportRequest(args1, l, STDOUT_FILENO);
- sp<ReportRequest> r2 = new ReportRequest(args2, l, STDOUT_FILENO);
+ sp<ReportRequest> r1 = new ReportRequest(args1, l, tf.fd);
+ sp<ReportRequest> r2 = new ReportRequest(args2, l, tf.fd);
reporter->batch.add(r1);
reporter->batch.add(r2);
- CaptureStdout();
ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
- EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x5" "abcde"));
+
+ string result;
+ ReadFileToString(tf.path, &result);
+ EXPECT_THAT(result, StrEq("\n\x5" "abcde"));
+
EXPECT_EQ(l->startInvoked, 2);
EXPECT_EQ(l->finishInvoked, 2);
EXPECT_TRUE(l->startSections.empty());
diff --git a/cmds/incidentd/tests/section_list.cpp b/cmds/incidentd/tests/section_list.cpp
index e47b61cf..4acc429 100644
--- a/cmds/incidentd/tests/section_list.cpp
+++ b/cmds/incidentd/tests/section_list.cpp
@@ -20,9 +20,11 @@
Privacy field_0 { 0, 11, list, EXPLICIT, NULL };
Privacy field_1 { 1, 9, NULL, AUTOMATIC, NULL };
-const Privacy* PRIVACY_POLICY_LIST[] = {
+Privacy* final_list[] = {
&field_0,
&field_1
};
-const int PRIVACY_POLICY_COUNT = 2;
\ No newline at end of file
+const Privacy** PRIVACY_POLICY_LIST = const_cast<const Privacy**>(final_list);
+
+const int PRIVACY_POLICY_COUNT = 2;
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 60ec8a9..9490880 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -116,9 +116,8 @@
}
public int run(String[] args) throws RemoteException {
- boolean validCommand = false;
if (args.length < 1) {
- return showUsage();
+ return runShellCommand("package", mArgs);
}
mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
@@ -134,18 +133,6 @@
String op = args[0];
mNextArg = 1;
- if ("list".equals(op)) {
- return runList();
- }
-
- if ("path".equals(op)) {
- return runPath();
- }
-
- if ("dump".equals(op)) {
- return runDump();
- }
-
if ("install".equals(op)) {
return runInstall();
}
@@ -166,138 +153,12 @@
return runInstallAbandon();
}
- if ("set-installer".equals(op)) {
- return runSetInstaller();
- }
-
- if ("uninstall".equals(op)) {
- return runUninstall();
- }
-
- if ("clear".equals(op)) {
- return runClear();
- }
-
- if ("enable".equals(op)) {
- return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
- }
-
- if ("disable".equals(op)) {
- return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
- }
-
- if ("disable-user".equals(op)) {
- return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
- }
-
- if ("disable-until-used".equals(op)) {
- return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
- }
-
- if ("default-state".equals(op)) {
- return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
- }
-
- if ("hide".equals(op)) {
- return runSetHiddenSetting(true);
- }
-
- if ("unhide".equals(op)) {
- return runSetHiddenSetting(false);
- }
-
- if ("grant".equals(op)) {
- return runGrantRevokePermission(true);
- }
-
- if ("revoke".equals(op)) {
- return runGrantRevokePermission(false);
- }
-
- if ("reset-permissions".equals(op)) {
- return runResetPermissions();
- }
-
- if ("set-permission-enforced".equals(op)) {
- return runSetPermissionEnforced();
- }
-
- if ("set-app-link".equals(op)) {
- return runSetAppLink();
- }
-
- if ("get-app-link".equals(op)) {
- return runGetAppLink();
- }
-
- if ("set-install-location".equals(op)) {
- return runSetInstallLocation();
- }
-
- if ("get-install-location".equals(op)) {
- return runGetInstallLocation();
- }
-
- if ("trim-caches".equals(op)) {
- return runTrimCaches();
- }
-
- if ("create-user".equals(op)) {
- return runCreateUser();
- }
-
- if ("remove-user".equals(op)) {
- return runRemoveUser();
- }
-
- if ("get-max-users".equals(op)) {
- return runGetMaxUsers();
- }
-
- if ("force-dex-opt".equals(op)) {
- return runForceDexOpt();
- }
-
- if ("move-package".equals(op)) {
- return runMovePackage();
- }
-
- if ("move-primary-storage".equals(op)) {
- return runMovePrimaryStorage();
- }
-
- if ("set-user-restriction".equals(op)) {
- return runSetUserRestriction();
- }
-
- try {
- if (args.length == 1) {
- if (args[0].equalsIgnoreCase("-l")) {
- validCommand = true;
- return runShellCommand("package", new String[] { "list", "package" });
- } else if (args[0].equalsIgnoreCase("-lf")) {
- validCommand = true;
- return runShellCommand("package", new String[] { "list", "package", "-f" });
- }
- } else if (args.length == 2) {
- if (args[0].equalsIgnoreCase("-p")) {
- validCommand = true;
- return displayPackageFilePath(args[1], UserHandle.USER_SYSTEM);
- }
- }
- return 1;
- } finally {
- if (validCommand == false) {
- if (op != null) {
- System.err.println("Error: unknown command '" + op + "'");
- }
- showUsage();
- }
- }
+ return runShellCommand("package", mArgs);
}
static final class MyShellCallback extends ShellCallback {
- @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
+ String mode) {
File file = new File(path);
final ParcelFileDescriptor fd;
try {
@@ -704,59 +565,6 @@
}
}
- /**
- * Execute the list sub-command.
- *
- * pm list [package | packages]
- * pm list permission-groups
- * pm list permissions
- * pm list features
- * pm list libraries
- * pm list instrumentation
- */
- private int runList() {
- final String type = nextArg();
- if ("users".equals(type)) {
- return runShellCommand("user", new String[] { "list" });
- }
- return runShellCommand("package", mArgs);
- }
-
- private int runUninstall() {
- return runShellCommand("package", mArgs);
- }
-
- private int runPath() {
- int userId = UserHandle.USER_SYSTEM;
- String option = nextOption();
- if (option != null && option.equals("--user")) {
- String optionData = nextOptionData();
- if (optionData == null || !isNumber(optionData)) {
- System.err.println("Error: no USER_ID specified");
- return showUsage();
- } else {
- userId = Integer.parseInt(optionData);
- }
- }
-
- String pkg = nextArg();
- if (pkg == null) {
- System.err.println("Error: no package specified");
- return 1;
- }
- return displayPackageFilePath(pkg, userId);
- }
-
- private int runDump() {
- String pkg = nextArg();
- if (pkg == null) {
- System.err.println("Error: no package specified");
- return 1;
- }
- ActivityManager.dumpPackageStateStatic(FileDescriptor.out, pkg);
- return 0;
- }
-
class LocalPackageInstallObserver extends PackageInstallObserver {
boolean finished;
int result;
@@ -779,477 +587,6 @@
}
}
- // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
- private int runSetAppLink() {
- int userId = UserHandle.USER_SYSTEM;
-
- String opt;
- while ((opt = nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = Integer.parseInt(nextOptionData());
- if (userId < 0) {
- System.err.println("Error: user must be >= 0");
- return 1;
- }
- } else {
- System.err.println("Error: unknown option: " + opt);
- return showUsage();
- }
- }
-
- // Package name to act on; required
- final String pkg = nextArg();
- if (pkg == null) {
- System.err.println("Error: no package specified.");
- return showUsage();
- }
-
- // State to apply; {always|ask|never|undefined}, required
- final String modeString = nextArg();
- if (modeString == null) {
- System.err.println("Error: no app link state specified.");
- return showUsage();
- }
-
- final int newMode;
- switch (modeString.toLowerCase()) {
- case "undefined":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- break;
-
- case "always":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
- break;
-
- case "ask":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
- break;
-
- case "always-ask":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
- break;
-
- case "never":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
- break;
-
- default:
- System.err.println("Error: unknown app link state '" + modeString + "'");
- return 1;
- }
-
- try {
- final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId);
- if (info == null) {
- System.err.println("Error: package " + pkg + " not found.");
- return 1;
- }
-
- if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
- System.err.println("Error: package " + pkg + " does not handle web links.");
- return 1;
- }
-
- if (!mPm.updateIntentVerificationStatus(pkg, newMode, userId)) {
- System.err.println("Error: unable to update app link status for " + pkg);
- return 1;
- }
- } catch (Exception e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
-
- return 0;
- }
-
- // pm get-app-link [--user USER_ID] PACKAGE
- private int runGetAppLink() {
- int userId = UserHandle.USER_SYSTEM;
-
- String opt;
- while ((opt = nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = Integer.parseInt(nextOptionData());
- if (userId < 0) {
- System.err.println("Error: user must be >= 0");
- return 1;
- }
- } else {
- System.err.println("Error: unknown option: " + opt);
- return showUsage();
- }
- }
-
- // Package name to act on; required
- final String pkg = nextArg();
- if (pkg == null) {
- System.err.println("Error: no package specified.");
- return showUsage();
- }
-
- try {
- final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId);
- if (info == null) {
- System.err.println("Error: package " + pkg + " not found.");
- return 1;
- }
-
- if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
- System.err.println("Error: package " + pkg + " does not handle web links.");
- return 1;
- }
-
- System.out.println(linkStateToString(mPm.getIntentVerificationStatus(pkg, userId)));
- } catch (Exception e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
-
- return 0;
- }
-
- private String linkStateToString(int state) {
- switch (state) {
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask";
- }
- return "Unknown link state: " + state;
- }
-
- private int runSetInstallLocation() {
- int loc;
-
- String arg = nextArg();
- if (arg == null) {
- System.err.println("Error: no install location specified.");
- return 1;
- }
- try {
- loc = Integer.parseInt(arg);
- } catch (NumberFormatException e) {
- System.err.println("Error: install location has to be a number.");
- return 1;
- }
- try {
- if (!mPm.setInstallLocation(loc)) {
- System.err.println("Error: install location has to be a number.");
- return 1;
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- private int runGetInstallLocation() {
- try {
- int loc = mPm.getInstallLocation();
- String locStr = "invalid";
- if (loc == PackageHelper.APP_INSTALL_AUTO) {
- locStr = "auto";
- } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
- locStr = "internal";
- } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
- locStr = "external";
- }
- System.out.println(loc + "[" + locStr + "]");
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- private int runSetInstaller() throws RemoteException {
- final String targetPackage = nextArg();
- final String installerPackageName = nextArg();
-
- if (targetPackage == null || installerPackageName == null) {
- throw new IllegalArgumentException(
- "must provide both target and installer package names");
- }
-
- mPm.setInstallerPackageName(targetPackage, installerPackageName);
- System.out.println("Success");
- return 0;
- }
-
- public int runCreateUser() {
- String name;
- int userId = -1;
- int flags = 0;
- String opt;
- while ((opt = nextOption()) != null) {
- if ("--profileOf".equals(opt)) {
- String optionData = nextOptionData();
- if (optionData == null || !isNumber(optionData)) {
- System.err.println("Error: no USER_ID specified");
- return showUsage();
- } else {
- userId = Integer.parseInt(optionData);
- }
- } else if ("--managed".equals(opt)) {
- flags |= UserInfo.FLAG_MANAGED_PROFILE;
- } else if ("--restricted".equals(opt)) {
- flags |= UserInfo.FLAG_RESTRICTED;
- } else if ("--ephemeral".equals(opt)) {
- flags |= UserInfo.FLAG_EPHEMERAL;
- } else if ("--guest".equals(opt)) {
- flags |= UserInfo.FLAG_GUEST;
- } else if ("--demo".equals(opt)) {
- flags |= UserInfo.FLAG_DEMO;
- } else {
- System.err.println("Error: unknown option " + opt);
- return showUsage();
- }
- }
- String arg = nextArg();
- if (arg == null) {
- System.err.println("Error: no user name specified.");
- return 1;
- }
- name = arg;
- try {
- UserInfo info;
- if ((flags & UserInfo.FLAG_RESTRICTED) != 0) {
- // In non-split user mode, userId can only be SYSTEM
- int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
- info = mUm.createRestrictedProfile(name, parentUserId);
- mAm.addSharedAccountsFromParentUser(parentUserId, userId,
- (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
- } else if (userId < 0) {
- info = mUm.createUser(name, flags);
- } else {
- info = mUm.createProfileForUser(name, flags, userId, null);
- }
-
- if (info != null) {
- System.out.println("Success: created user id " + info.id);
- return 0;
- } else {
- System.err.println("Error: couldn't create User.");
- return 1;
- }
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- public int runRemoveUser() {
- int userId;
- String arg = nextArg();
- if (arg == null) {
- System.err.println("Error: no user id specified.");
- return 1;
- }
- try {
- userId = Integer.parseInt(arg);
- } catch (NumberFormatException e) {
- System.err.println("Error: user id '" + arg + "' is not a number.");
- return 1;
- }
- try {
- if (mUm.removeUser(userId)) {
- System.out.println("Success: removed user");
- return 0;
- } else {
- System.err.println("Error: couldn't remove user id " + userId);
- return 1;
- }
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- public int runGetMaxUsers() {
- System.out.println("Maximum supported users: " + UserManager.getMaxSupportedUsers());
- return 0;
- }
-
- public int runForceDexOpt() {
- final String packageName = nextArg();
- try {
- mPm.forceDexOpt(packageName);
- return 0;
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- public int runMovePackage() {
- final String packageName = nextArg();
- String volumeUuid = nextArg();
- if ("internal".equals(volumeUuid)) {
- volumeUuid = null;
- }
-
- try {
- final int moveId = mPm.movePackage(packageName, volumeUuid);
-
- int status = mPm.getMoveStatus(moveId);
- while (!PackageManager.isMoveStatusFinished(status)) {
- SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
- status = mPm.getMoveStatus(moveId);
- }
-
- if (status == PackageManager.MOVE_SUCCEEDED) {
- System.out.println("Success");
- return 0;
- } else {
- System.err.println("Failure [" + status + "]");
- return 1;
- }
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- public int runMovePrimaryStorage() {
- String volumeUuid = nextArg();
- if ("internal".equals(volumeUuid)) {
- volumeUuid = null;
- }
-
- try {
- final int moveId = mPm.movePrimaryStorage(volumeUuid);
-
- int status = mPm.getMoveStatus(moveId);
- while (!PackageManager.isMoveStatusFinished(status)) {
- SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
- status = mPm.getMoveStatus(moveId);
- }
-
- if (status == PackageManager.MOVE_SUCCEEDED) {
- System.out.println("Success");
- return 0;
- } else {
- System.err.println("Failure [" + status + "]");
- return 1;
- }
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- public int runSetUserRestriction() {
- int userId = UserHandle.USER_SYSTEM;
- String opt = nextOption();
- if (opt != null && "--user".equals(opt)) {
- String arg = nextArg();
- if (arg == null || !isNumber(arg)) {
- System.err.println("Error: valid userId not specified");
- return 1;
- }
- userId = Integer.parseInt(arg);
- }
-
- String restriction = nextArg();
- String arg = nextArg();
- boolean value;
- if ("1".equals(arg)) {
- value = true;
- } else if ("0".equals(arg)) {
- value = false;
- } else {
- System.err.println("Error: valid value not specified");
- return 1;
- }
- try {
- mUm.setUserRestriction(restriction, value, userId);
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- return 1;
- }
- }
-
- static class ClearDataObserver extends IPackageDataObserver.Stub {
- boolean finished;
- boolean result;
-
- @Override
- public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
- synchronized (this) {
- finished = true;
- result = succeeded;
- notifyAll();
- }
- }
- }
-
- private int runClear() {
- int userId = UserHandle.USER_SYSTEM;
- String option = nextOption();
- if (option != null && option.equals("--user")) {
- String optionData = nextOptionData();
- if (optionData == null || !isNumber(optionData)) {
- System.err.println("Error: no USER_ID specified");
- return showUsage();
- } else {
- userId = Integer.parseInt(optionData);
- }
- }
-
- String pkg = nextArg();
- if (pkg == null) {
- System.err.println("Error: no package specified");
- return showUsage();
- }
-
- ClearDataObserver obs = new ClearDataObserver();
- try {
- ActivityManager.getService().clearApplicationUserData(pkg, obs, userId);
- synchronized (obs) {
- while (!obs.finished) {
- try {
- obs.wait();
- } catch (InterruptedException e) {
- }
- }
- }
-
- if (obs.result) {
- System.out.println("Success");
- return 0;
- } else {
- System.err.println("Failed");
- return 1;
- }
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- private static String enabledSettingToString(int state) {
- switch (state) {
- case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
- return "default";
- case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
- return "enabled";
- case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
- return "disabled";
- case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
- return "disabled-user";
- case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
- return "disabled-until-used";
- }
- return "unknown";
- }
-
private static boolean isNumber(String s) {
try {
Integer.parseInt(s);
@@ -1259,169 +596,6 @@
return true;
}
- private int runSetEnabledSetting(int state) {
- int userId = UserHandle.USER_SYSTEM;
- String option = nextOption();
- if (option != null && option.equals("--user")) {
- String optionData = nextOptionData();
- if (optionData == null || !isNumber(optionData)) {
- System.err.println("Error: no USER_ID specified");
- return showUsage();
- } else {
- userId = Integer.parseInt(optionData);
- }
- }
-
- String pkg = nextArg();
- if (pkg == null) {
- System.err.println("Error: no package or component specified");
- return showUsage();
- }
- ComponentName cn = ComponentName.unflattenFromString(pkg);
- if (cn == null) {
- try {
- mPm.setApplicationEnabledSetting(pkg, state, 0, userId,
- "shell:" + android.os.Process.myUid());
- System.out.println("Package " + pkg + " new state: "
- + enabledSettingToString(
- mPm.getApplicationEnabledSetting(pkg, userId)));
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- } else {
- try {
- mPm.setComponentEnabledSetting(cn, state, 0, userId);
- System.out.println("Component " + cn.toShortString() + " new state: "
- + enabledSettingToString(
- mPm.getComponentEnabledSetting(cn, userId)));
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
- }
-
- private int runSetHiddenSetting(boolean state) {
- int userId = UserHandle.USER_SYSTEM;
- String option = nextOption();
- if (option != null && option.equals("--user")) {
- String optionData = nextOptionData();
- if (optionData == null || !isNumber(optionData)) {
- System.err.println("Error: no USER_ID specified");
- return showUsage();
- } else {
- userId = Integer.parseInt(optionData);
- }
- }
-
- String pkg = nextArg();
- if (pkg == null) {
- System.err.println("Error: no package or component specified");
- return showUsage();
- }
- try {
- mPm.setApplicationHiddenSettingAsUser(pkg, state, userId);
- System.out.println("Package " + pkg + " new hidden state: "
- + mPm.getApplicationHiddenSettingAsUser(pkg, userId));
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- private int runGrantRevokePermission(boolean grant) {
- int userId = UserHandle.USER_SYSTEM;
-
- String opt = null;
- while ((opt = nextOption()) != null) {
- if (opt.equals("--user")) {
- userId = Integer.parseInt(nextArg());
- }
- }
-
- String pkg = nextArg();
- if (pkg == null) {
- System.err.println("Error: no package specified");
- return showUsage();
- }
- String perm = nextArg();
- if (perm == null) {
- System.err.println("Error: no permission specified");
- return showUsage();
- }
-
- try {
- if (grant) {
- mPm.grantRuntimePermission(pkg, perm, userId);
- } else {
- mPm.revokeRuntimePermission(pkg, perm, userId);
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- } catch (IllegalArgumentException e) {
- System.err.println("Bad argument: " + e.toString());
- return showUsage();
- } catch (SecurityException e) {
- System.err.println("Operation not allowed: " + e.toString());
- return 1;
- }
- }
-
- private int runResetPermissions() {
- try {
- mPm.resetRuntimePermissions();
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- } catch (IllegalArgumentException e) {
- System.err.println("Bad argument: " + e.toString());
- return showUsage();
- } catch (SecurityException e) {
- System.err.println("Operation not allowed: " + e.toString());
- return 1;
- }
- }
-
- private int runSetPermissionEnforced() {
- final String permission = nextArg();
- if (permission == null) {
- System.err.println("Error: no permission specified");
- return showUsage();
- }
- final String enforcedRaw = nextArg();
- if (enforcedRaw == null) {
- System.err.println("Error: no enforcement specified");
- return showUsage();
- }
- final boolean enforced = Boolean.parseBoolean(enforcedRaw);
- try {
- mPm.setPermissionEnforced(permission, enforced);
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- } catch (IllegalArgumentException e) {
- System.err.println("Bad argument: " + e.toString());
- return showUsage();
- } catch (SecurityException e) {
- System.err.println("Operation not allowed: " + e.toString());
- return 1;
- }
- }
-
static class ClearCacheObserver extends IPackageDataObserver.Stub {
boolean finished;
boolean result;
@@ -1437,62 +611,17 @@
}
- private int runTrimCaches() {
- String size = nextArg();
- if (size == null) {
- System.err.println("Error: no size specified");
- return showUsage();
- }
- long multiplier = 1;
- int len = size.length();
- char c = size.charAt(len - 1);
- if (c < '0' || c > '9') {
- if (c == 'K' || c == 'k') {
- multiplier = 1024L;
- } else if (c == 'M' || c == 'm') {
- multiplier = 1024L*1024L;
- } else if (c == 'G' || c == 'g') {
- multiplier = 1024L*1024L*1024L;
- } else {
- System.err.println("Invalid suffix: " + c);
- return showUsage();
+ static class ClearDataObserver extends IPackageDataObserver.Stub {
+ boolean finished;
+ boolean result;
+
+ @Override
+ public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
+ synchronized (this) {
+ finished = true;
+ result = succeeded;
+ notifyAll();
}
- size = size.substring(0, len-1);
- }
- long sizeVal;
- try {
- sizeVal = Long.parseLong(size) * multiplier;
- } catch (NumberFormatException e) {
- System.err.println("Error: expected number at: " + size);
- return showUsage();
- }
- String volumeUuid = nextArg();
- if ("internal".equals(volumeUuid)) {
- volumeUuid = null;
- }
- ClearDataObserver obs = new ClearDataObserver();
- try {
- mPm.freeStorageAndNotify(volumeUuid, sizeVal,
- StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs);
- synchronized (obs) {
- while (!obs.finished) {
- try {
- obs.wait();
- } catch (InterruptedException e) {
- }
- }
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- } catch (IllegalArgumentException e) {
- System.err.println("Bad argument: " + e.toString());
- return showUsage();
- } catch (SecurityException e) {
- System.err.println("Operation not allowed: " + e.toString());
- return 1;
}
}
diff --git a/cmds/statsd/.clang-format b/cmds/statsd/.clang-format
index 3d64bee..cead3a0 100644
--- a/cmds/statsd/.clang-format
+++ b/cmds/statsd/.clang-format
@@ -12,3 +12,6 @@
PointerAlignment: Left
TabWidth: 4
AccessModifierOffset: -4
+IncludeCategories:
+ - Regex: '^"Log\.h"'
+ Priority: -1
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 8505d4c..54ade35 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -15,6 +15,7 @@
LOCAL_PATH:= $(call my-dir)
statsd_common_src := \
+ ../../core/java/android/os/IStatsCallbacks.aidl \
../../core/java/android/os/IStatsCompanionService.aidl \
../../core/java/android/os/IStatsManager.aidl \
src/stats_log.proto \
@@ -37,8 +38,13 @@
src/matchers/matcher_util.cpp \
src/matchers/SimpleLogMatchingTracker.cpp \
src/metrics/CountAnomalyTracker.cpp \
+ src/metrics/MetricProducer.cpp \
+ src/metrics/EventMetricProducer.cpp \
src/metrics/CountMetricProducer.cpp \
src/metrics/DurationMetricProducer.cpp \
+ src/metrics/duration_helper/OringDurationTracker.cpp \
+ src/metrics/duration_helper/MaxDurationTracker.cpp \
+ src/metrics/ValueMetricProducer.cpp \
src/metrics/MetricsManager.cpp \
src/metrics/metrics_manager_util.cpp \
src/packages/UidMap.cpp \
@@ -64,7 +70,8 @@
libselinux \
libutils \
libservices \
- libandroidfw
+ libandroidfw \
+ libprotoutil
# =========
# statsd
@@ -99,7 +106,8 @@
LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
LOCAL_C_INCLUDES += $(statsd_common_c_includes)
-LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries)
+LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
+ libgtest_prod
LOCAL_MODULE_CLASS := EXECUTABLES
@@ -138,7 +146,9 @@
tests/LogEntryMatcher_test.cpp \
tests/LogReader_test.cpp \
tests/MetricsManager_test.cpp \
- tests/UidMap_test.cpp
+ tests/UidMap_test.cpp \
+ tests/OringDurationTracker_test.cpp \
+ tests/MaxDurationTracker_test.cpp
LOCAL_STATIC_LIBRARIES := \
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index e7825cf..c0cedb1 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -33,8 +33,9 @@
namespace os {
namespace statsd {
-StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap)
- : m_dropbox_writer("all-logs"), mUidMap(uidMap) {
+StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
+ const std::function<void(const vector<uint8_t>&)>& pushLog)
+ : mUidMap(uidMap), mPushLog(pushLog) {
}
StatsLogProcessor::~StatsLogProcessor() {
@@ -42,15 +43,10 @@
// TODO: what if statsd service restarts? How do we know what logs are already processed before?
void StatsLogProcessor::OnLogEvent(const LogEvent& msg) {
- // TODO: Use EventMetric to filter the events we want to log.
- /* TODO: Convert this when we have the generic protobuf writing library in.
- EventMetricData eventMetricData = parse(msg);
- m_dropbox_writer.addEventMetricData(eventMetricData);
- */
-
// pass the event to metrics managers.
for (auto& pair : mMetricsManagers) {
pair.second->onLogEvent(msg);
+ flushIfNecessary(msg.GetTimestampNs(), pair.first, pair.second);
}
}
@@ -64,6 +60,7 @@
unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(config);
if (newMetricsManager->isConfigValid()) {
+ mUidMap->OnConfigUpdated(key);
mMetricsManagers[key] = std::move(newMetricsManager);
// Why doesn't this work? mMetricsManagers.insert({key, std::move(newMetricsManager)});
ALOGD("StatsdConfig valid");
@@ -73,14 +70,27 @@
}
}
-vector<StatsLogReport> StatsLogProcessor::onDumpReport(const ConfigKey& key) {
+ConfigMetricsReport StatsLogProcessor::onDumpReport(const ConfigKey& key) {
+ ConfigMetricsReport report;
+
auto it = mMetricsManagers.find(key);
if (it == mMetricsManagers.end()) {
ALOGW("Config source %s does not exist", key.ToString().c_str());
- return vector<StatsLogReport>();
+ return report;
}
- return it->second->onDumpReport();
+ auto set_key = report.mutable_config_key();
+ set_key->set_uid(key.GetUid());
+ set_key->set_name(key.GetName());
+ for (auto m : it->second->onDumpReport()) {
+ // Transfer the vector of StatsLogReport into a field
+ // TODO: perhaps we just have bytes being returned from onDumpReport and transfer bytes
+ auto dest = report.add_metrics();
+ *dest = m;
+ }
+ auto temp = mUidMap->getOutput(key);
+ report.set_allocated_uid_map(&temp);
+ return report;
}
void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
@@ -88,7 +98,42 @@
if (it != mMetricsManagers.end()) {
it->second->finish();
mMetricsManagers.erase(it);
+ mUidMap->OnConfigRemoved(key);
}
+ auto flushTime = mLastFlushTimes.find(key);
+ if (flushTime != mLastFlushTimes.end()) {
+ mLastFlushTimes.erase(flushTime);
+ }
+}
+
+void StatsLogProcessor::flushIfNecessary(uint64_t timestampNs,
+ const ConfigKey& key,
+ const unique_ptr<MetricsManager>& metricsManager) {
+ auto lastFlushNs = mLastFlushTimes.find(key);
+ if (lastFlushNs != mLastFlushTimes.end()) {
+ if (timestampNs - lastFlushNs->second < kMinFlushPeriod) {
+ return;
+ }
+ }
+
+ size_t totalBytes = metricsManager->byteSize();
+ if (totalBytes > kMaxSerializedBytes) {
+ flush();
+ mLastFlushTimes[key] = std::move(timestampNs);
+ }
+}
+
+void StatsLogProcessor::flush() {
+ // TODO: Take ConfigKey as an argument and flush metrics related to the
+ // ConfigKey. Also, create a wrapper that holds a repeated field of
+ // StatsLogReport's.
+ /*
+ StatsLogReport logReport;
+ const int numBytes = logReport.ByteSize();
+ vector<uint8_t> logReportBuffer(numBytes);
+ logReport.SerializeToArray(&logReportBuffer[0], numBytes);
+ mPushLog(logReportBuffer);
+ */
}
} // namespace statsd
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 3cefd29..0083827 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -20,7 +20,6 @@
#include "logd/LogReader.h"
#include "metrics/MetricsManager.h"
#include "packages/UidMap.h"
-#include "storage/DropboxWriter.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
@@ -33,7 +32,8 @@
class StatsLogProcessor : public ConfigListener {
public:
- StatsLogProcessor(const sp<UidMap>& uidMap);
+ StatsLogProcessor(const sp<UidMap>& uidMap,
+ const std::function<void(const vector<uint8_t>&)>& pushLog);
virtual ~StatsLogProcessor();
virtual void OnLogEvent(const LogEvent& event);
@@ -42,15 +42,36 @@
void OnConfigRemoved(const ConfigKey& key);
// TODO: Once we have the ProtoOutputStream in c++, we can just return byte array.
- std::vector<StatsLogReport> onDumpReport(const ConfigKey& key);
+ ConfigMetricsReport onDumpReport(const ConfigKey& key);
+
+ /* Request a flush through a binder call. */
+ void flush();
private:
- // TODO: use EventMetrics to log the events.
- DropboxWriter m_dropbox_writer;
-
std::unordered_map<ConfigKey, std::unique_ptr<MetricsManager>> mMetricsManagers;
+ std::unordered_map<ConfigKey, long> mLastFlushTimes;
+
sp<UidMap> mUidMap; // Reference to the UidMap to lookup app name and version for each uid.
+
+ /* Max *serialized* size of the logs kept in memory before flushing through binder call.
+ Proto lite does not implement the SpaceUsed() function which gives the in memory byte size.
+ So we cap memory usage by limiting the serialized size. Note that protobuf's in memory size
+ is higher than its serialized size.
+ */
+ static const size_t kMaxSerializedBytes = 16 * 1024;
+
+ /* Check if the buffer size exceeds the max buffer size when the new entry is added, and flush
+ the logs to callback clients if true. */
+ void flushIfNecessary(uint64_t timestampNs,
+ const ConfigKey& key,
+ const unique_ptr<MetricsManager>& metricsManager);
+
+ std::function<void(const vector<uint8_t>&)> mPushLog;
+
+ /* Minimum period between two flushes in nanoseconds. Currently set to 10
+ * minutes. */
+ static const unsigned long long kMinFlushPeriod = 600 * NS_PER_SEC;
};
} // namespace statsd
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 1faeee0..edb1a0f 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -65,10 +65,11 @@
StatsService::StatsService(const sp<Looper>& handlerLooper)
: mAnomalyMonitor(new AnomalyMonitor(2)) // TODO: Put this comment somewhere better
{
- mStatsPullerManager = new StatsPullerManager();
mUidMap = new UidMap();
mConfigManager = new ConfigManager();
- mProcessor = new StatsLogProcessor(mUidMap);
+ mProcessor = new StatsLogProcessor(mUidMap, [this](const vector<uint8_t>& log) {
+ pushLog(log);
+ });
mConfigManager->AddListener(mProcessor);
@@ -229,6 +230,14 @@
fprintf(out, " parameter on eng builds. If UID is omitted the calling\n");
fprintf(out, " uid is used.\n");
fprintf(out, " NAME The per-uid name to use\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
+ fprintf(out, "usage: adb shell cmd stats dump-report [UID] NAME\n");
+ fprintf(out, " Dump all metric data for a configuration.\n");
+ fprintf(out, " UID The uid of the configuration. It is only possible to pass\n");
+ fprintf(out, " the UID parameter on eng builds. If UID is omitted the\n");
+ fprintf(out, " calling uid is used.\n");
+ fprintf(out, " NAME The name of the configuration\n");
}
status_t StatsService::cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8>& args) {
@@ -310,7 +319,7 @@
// Automatically pick the UID
uid = IPCThreadState::self()->getCallingUid();
// TODO: What if this isn't a binder call? Should we fail?
- name.assign(args[2].c_str(), args[2].size());
+ name.assign(args[1].c_str(), args[1].size());
good = true;
} else if (argCount == 3) {
// If it's a userdebug or eng build, then the shell user can
@@ -364,7 +373,7 @@
status_t StatsService::cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args) {
int s = atoi(args[1].c_str());
- auto stats = mStatsPullerManager->Pull(s);
+ auto stats = m_stats_puller_manager.Pull(s, time(nullptr));
for (const auto& it : stats) {
fprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str());
}
@@ -431,8 +440,9 @@
"Only system uid can call informPollAlarmFired");
}
+ m_stats_puller_manager.OnAlarmFired();
+
if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded");
- // TODO: determine what services to poll and poll (or ask StatsCompanionService to poll) them.
return Status::ok();
}
@@ -507,6 +517,40 @@
mProcessor->OnLogEvent(event);
}
+Status StatsService::requestPush() {
+ mProcessor->flush();
+ return Status::ok();
+}
+
+Status StatsService::pushLog(const vector<uint8_t>& log) {
+ std::lock_guard<std::mutex> lock(mLock);
+ for (size_t i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks[i]->onReceiveLogs((vector<uint8_t>*)&log);
+ }
+ return Status::ok();
+}
+
+Status StatsService::subscribeStatsLog(const sp<IStatsCallbacks>& callback) {
+ std::lock_guard<std::mutex> lock(mLock);
+ for (size_t i = 0; i < mCallbacks.size(); i++) {
+ if (mCallbacks[i] == callback) {
+ return Status::fromStatusT(-errno);
+ }
+ }
+ mCallbacks.add(callback);
+ IInterface::asBinder(callback)->linkToDeath(this);
+ return Status::ok();
+}
+
+void StatsService::binderDied(const wp<IBinder>& who) {
+ for (size_t i = 0; i < mCallbacks.size(); i++) {
+ if (IInterface::asBinder(mCallbacks[i]) == who) {
+ mCallbacks.removeAt(i);
+ break;
+ }
+ }
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 449a2b8..3930d31 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -24,9 +24,9 @@
#include "packages/UidMap.h"
#include <android/os/BnStatsManager.h>
+#include <android/os/IStatsCallbacks.h>
#include <android/os/IStatsCompanionService.h>
#include <binder/IResultReceiver.h>
-#include <binder/IShellCallback.h>
#include <utils/Looper.h>
#include <deque>
@@ -42,7 +42,7 @@
namespace os {
namespace statsd {
-class StatsService : public BnStatsManager, public LogListener {
+class StatsService : public BnStatsManager, public LogListener, public IBinder::DeathRecipient {
public:
StatsService(const sp<Looper>& handlerLooper);
virtual ~StatsService();
@@ -70,6 +70,22 @@
*/
virtual void OnLogEvent(const LogEvent& event);
+ /**
+ * Binder call to force trigger pushLog. This would be called by callback
+ * clients.
+ */
+ virtual Status requestPush() override;
+
+ /**
+ * Pushes stats log entries from statsd to callback clients.
+ */
+ Status pushLog(const vector<uint8_t>& log);
+
+ /**
+ * Binder call to listen to statsd to send stats log entries.
+ */
+ virtual Status subscribeStatsLog(const sp<IStatsCallbacks>& callbacks) override;
+
// TODO: public for testing since statsd doesn't run when system starts. Change to private
// later.
/** Inform statsCompanion that statsd is ready. */
@@ -78,6 +94,9 @@
/** Fetches and returns the StatsCompanionService. */
static sp<IStatsCompanionService> getStatsCompanionService();
+ /** IBinder::DeathRecipient */
+ virtual void binderDied(const wp<IBinder>& who) override;
+
private:
/**
* Load system properties at init.
@@ -138,7 +157,7 @@
/**
* Fetches external metrics.
*/
- sp<StatsPullerManager> mStatsPullerManager;
+ StatsPullerManager& m_stats_puller_manager = StatsPullerManager::GetInstance();
/**
* Tracks the configurations that have been passed to statsd.
@@ -159,6 +178,16 @@
* Whether this is an eng build.
*/
bool mEngBuild;
+
+ /**
+ * Lock for callback handling.
+ */
+ std::mutex mLock;
+
+ /**
+ * Vector maintaining the list of callbacks for clients.
+ */
+ Vector< sp<IStatsCallbacks> > mCallbacks;
};
} // namespace statsd
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index f56c15a..953bcb3 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -16,7 +16,6 @@
#define DEBUG true // STOPSHIP if true
#include "Log.h"
-
#include "CombinationConditionTracker.h"
#include <log/logprint.h>
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index fc88a88..dbdb3b7 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -46,8 +46,6 @@
const std::vector<sp<ConditionTracker>>& allConditions,
std::vector<ConditionState>& conditionCache) override;
- void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override{};
-
private:
LogicalOperation mLogicalOperation;
// Store index of the children Conditions.
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 055b478..bb5ddeb 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -16,8 +16,6 @@
#pragma once
-#include "Log.h"
-
#include "condition/condition_util.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "matchers/LogMatchingTracker.h"
@@ -103,8 +101,6 @@
mSliced = mSliced | sliced;
}
- virtual void addDimensions(const std::vector<KeyMatcher>& keyMatchers) = 0;
-
protected:
// We don't really need the string name, but having a name here makes log messages
// easy to debug.
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
index 4889b64..1a01afa 100644
--- a/cmds/statsd/src/condition/ConditionWizard.h
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -27,19 +27,23 @@
// Held by MetricProducer, to query a condition state with input defined in EventConditionLink.
class ConditionWizard : public virtual android::RefBase {
public:
+ ConditionWizard(){}; // for testing
ConditionWizard(std::vector<sp<ConditionTracker>>& conditionTrackers)
: mAllConditions(conditionTrackers){};
+ virtual ~ConditionWizard(){};
+
// Query condition state, for a ConditionTracker at [conditionIndex], with [conditionParameters]
// [conditionParameters] mapping from condition name to the HashableDimensionKey to query the
// condition.
// The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case,
// the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
- ConditionState query(const int conditionIndex,
- const std::map<std::string, HashableDimensionKey>& conditionParameters);
+ virtual ConditionState query(
+ const int conditionIndex,
+ const std::map<std::string, HashableDimensionKey>& conditionParameters);
private:
- std::vector<sp<ConditionTracker>>& mAllConditions;
+ std::vector<sp<ConditionTracker>> mAllConditions;
};
} // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index bde3846..b691faea 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "SimpleConditionTracker.h"
@@ -74,6 +74,13 @@
mStopAllLogMatcherIndex = -1;
}
+ mDimension.insert(mDimension.begin(), simpleCondition.dimension().begin(),
+ simpleCondition.dimension().end());
+
+ if (mDimension.size() > 0) {
+ mSliced = true;
+ }
+
mInitialized = true;
}
@@ -98,12 +105,6 @@
}
}
-void SimpleConditionTracker::addDimensions(const std::vector<KeyMatcher>& keyMatchers) {
- VLOG("Added dimensions size %lu", (unsigned long)keyMatchers.size());
- mDimensionsList.push_back(keyMatchers);
- mSliced = true;
-}
-
bool SimpleConditionTracker::evaluateCondition(const LogEvent& event,
const vector<MatchingState>& eventMatcherValues,
const vector<sp<ConditionTracker>>& mAllConditions,
@@ -157,18 +158,15 @@
// TODO: handle stop all; all dimension should be cleared.
}
- if (mDimensionsList.size() > 0) {
- for (size_t i = 0; i < mDimensionsList.size(); i++) {
- const auto& dim = mDimensionsList[i];
- vector<KeyValuePair> key = getDimensionKey(event, dim);
- HashableDimensionKey hashableKey = getHashableKey(key);
- if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
- mSlicedConditionState[hashableKey] != newCondition) {
- slicedChanged = true;
- mSlicedConditionState[hashableKey] = newCondition;
- }
- VLOG("key: %s %d", hashableKey.c_str(), newCondition);
+
+ if (mDimension.size() > 0) {
+ HashableDimensionKey hashableKey = getHashableKey(getDimensionKey(event, mDimension));
+ if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
+ mSlicedConditionState[hashableKey] != newCondition) {
+ slicedChanged = true;
+ mSlicedConditionState[hashableKey] = newCondition;
}
+ VLOG("key: %s %d", hashableKey.c_str(), newCondition);
// dump all dimensions for debugging
if (DEBUG) {
print(mSlicedConditionState, mName);
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index 1f357f0..b72157b 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -27,8 +27,6 @@
class SimpleConditionTracker : public virtual ConditionTracker {
public:
- // dimensions is a vector of vector because for one single condition, different metrics may be
- // interested in slicing in different ways. one vector<KeyMatcher> defines one type of slicing.
SimpleConditionTracker(const std::string& name, const int index,
const SimpleCondition& simpleCondition,
const std::unordered_map<std::string, int>& trackerNameIndexMap);
@@ -51,8 +49,6 @@
const std::vector<sp<ConditionTracker>>& allConditions,
std::vector<ConditionState>& conditionCache) override;
- void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override;
-
private:
// The index of the LogEventMatcher which defines the start.
int mStartLogMatcherIndex;
@@ -66,8 +62,11 @@
// The index of the LogEventMatcher which defines the stop all.
int mStopAllLogMatcherIndex;
- // Different metrics may subscribe to different types of slicings. So it's a vector of vector.
- std::vector<std::vector<KeyMatcher>> mDimensionsList;
+ // The dimension defines at the atom level, how start and stop should match.
+ // e.g., APP_IN_FOREGROUND, the dimension should be the uid field. Each "start" and
+ // "stop" tells you the state change of a particular app. Without this dimension, this
+ // condition does not make sense.
+ std::vector<KeyMatcher> mDimension;
// Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
// that StatsLogReport wants.
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 038edd3..8812719 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -118,9 +118,10 @@
StatsdConfig config;
config.set_config_id(12345L);
- int WAKE_LOCK_TAG_ID = 11;
+ int WAKE_LOCK_TAG_ID = 1111; // put a fake id here to make testing easier.
int WAKE_LOCK_UID_KEY_ID = 1;
- int WAKE_LOCK_STATE_KEY = 2;
+ int WAKE_LOCK_NAME_KEY = 3;
+ int WAKE_LOCK_STATE_KEY = 4;
int WAKE_LOCK_ACQUIRE_VALUE = 1;
int WAKE_LOCK_RELEASE_VALUE = 0;
@@ -130,20 +131,29 @@
int APP_USAGE_FOREGROUND = 1;
int APP_USAGE_BACKGROUND = 0;
- int SCREEN_EVENT_TAG_ID = 2;
+ int SCREEN_EVENT_TAG_ID = 29;
int SCREEN_EVENT_STATE_KEY = 1;
int SCREEN_EVENT_ON_VALUE = 2;
int SCREEN_EVENT_OFF_VALUE = 1;
- int UID_PROCESS_STATE_TAG_ID = 3;
+ int UID_PROCESS_STATE_TAG_ID = 27;
int UID_PROCESS_STATE_UID_KEY = 1;
+ int KERNEL_WAKELOCK_TAG_ID = 41;
+ int KERNEL_WAKELOCK_NAME_KEY = 4;
+
// Count Screen ON events.
CountMetric* metric = config.add_count_metric();
metric->set_metric_id(1);
metric->set_what("SCREEN_TURNED_ON");
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ // Anomaly threshold for screen-on count.
+ Alert* alert = metric->add_alerts();
+ alert->set_number_of_buckets(6);
+ alert->set_trigger_if_sum_gt(10);
+ alert->set_refractory_period_secs(30);
+
// Count process state changes, slice by uid.
metric = config.add_count_metric();
metric->set_metric_id(2);
@@ -161,7 +171,7 @@
keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
metric->set_condition("SCREEN_IS_OFF");
- // Count wake lock, slice by uid, while SCREEN_IS_OFF and app in background
+ // Count wake lock, slice by uid, while SCREEN_IS_ON and app in background
metric = config.add_count_metric();
metric->set_metric_id(4);
metric->set_what("APP_GET_WL");
@@ -174,21 +184,69 @@
link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
- // Duration of an app holding wl, while screen on and app in background
+ // Duration of an app holding any wl, while screen on and app in background, slice by uid
DurationMetric* durationMetric = config.add_duration_metric();
durationMetric->set_metric_id(5);
- durationMetric->set_start("APP_GET_WL");
- durationMetric->set_stop("APP_RELEASE_WL");
durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
keyMatcher = durationMetric->add_dimension();
keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+ durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
link = durationMetric->add_links();
link->set_condition("APP_IS_BACKGROUND");
link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+ // max Duration of an app holding any wl, while screen on and app in background, slice by uid
+ durationMetric = config.add_duration_metric();
+ durationMetric->set_metric_id(6);
+ durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
+ keyMatcher = durationMetric->add_dimension();
+ keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+ durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+ durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ link = durationMetric->add_links();
+ link->set_condition("APP_IS_BACKGROUND");
+ link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+ link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+ // Duration of an app holding any wl, while screen on and app in background
+ durationMetric = config.add_duration_metric();
+ durationMetric->set_metric_id(7);
+ durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
+ durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+ durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ link = durationMetric->add_links();
+ link->set_condition("APP_IS_BACKGROUND");
+ link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+ link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+ // Duration of screen on time.
+ durationMetric = config.add_duration_metric();
+ durationMetric->set_metric_id(8);
+ durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
+ durationMetric->set_what("SCREEN_IS_ON");
+
+ // Value metric to count KERNEL_WAKELOCK when screen turned on
+ ValueMetric* valueMetric = config.add_value_metric();
+ valueMetric->set_metric_id(6);
+ valueMetric->set_what("KERNEL_WAKELOCK");
+ valueMetric->set_value_field(1);
+ valueMetric->set_condition("SCREEN_IS_ON");
+ keyMatcher = valueMetric->add_dimension();
+ keyMatcher->set_key(KERNEL_WAKELOCK_NAME_KEY);
+ // This is for testing easier. We should never set bucket size this small.
+ valueMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
+
+ // Add an EventMetric to log process state change events.
+ EventMetric* eventMetric = config.add_event_metric();
+ eventMetric->set_metric_id(9);
+ eventMetric->set_what("SCREEN_TURNED_ON");
+
// Event matchers............
LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
eventMatcher->set_name("SCREEN_TURNED_ON");
@@ -261,6 +319,8 @@
simpleCondition = condition->mutable_simple_condition();
simpleCondition->set_start("APP_GOES_BACKGROUND");
simpleCondition->set_stop("APP_GOES_FOREGROUND");
+ KeyMatcher* condition_dimension1 = simpleCondition->add_dimension();
+ condition_dimension1->set_key(APP_USAGE_UID_KEY_ID);
condition = config.add_condition();
condition->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
@@ -269,6 +329,16 @@
combination_condition->add_condition("APP_IS_BACKGROUND");
combination_condition->add_condition("SCREEN_IS_ON");
+ condition = config.add_condition();
+ condition->set_name("WL_STATE_PER_APP_PER_NAME");
+ simpleCondition = condition->mutable_simple_condition();
+ simpleCondition->set_start("APP_GET_WL");
+ simpleCondition->set_stop("APP_RELEASE_WL");
+ KeyMatcher* condition_dimension = simpleCondition->add_dimension();
+ condition_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
+ condition_dimension = simpleCondition->add_dimension();
+ condition_dimension->set_key(WAKE_LOCK_NAME_KEY);
+
return config;
}
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.cpp b/cmds/statsd/src/external/KernelWakelockPuller.cpp
index ee072f8..00259a8 100644
--- a/cmds/statsd/src/external/KernelWakelockPuller.cpp
+++ b/cmds/statsd/src/external/KernelWakelockPuller.cpp
@@ -14,14 +14,14 @@
* limitations under the License.
*/
+#define DEBUG true
#include "Log.h"
#include <android/os/IStatsCompanionService.h>
#include <binder/IPCThreadState.h>
#include <private/android_filesystem_config.h>
+#include "KernelWakelockPuller.h"
#include "StatsService.h"
-#include "external/KernelWakelockPuller.h"
-#include "external/StatsPuller.h"
using namespace android;
using namespace android::base;
@@ -33,11 +33,11 @@
namespace os {
namespace statsd {
-const int KernelWakelockPuller::PULL_CODE_KERNEL_WAKELOCKS = 20;
+const int KernelWakelockPuller::PULL_CODE_KERNEL_WAKELOCKS = 1004;
// The reading and parsing are implemented in Java. It is not difficult to port over. But for now
// let StatsCompanionService handle that and send the data back.
-vector<StatsLogEventWrapper> KernelWakelockPuller::pull() {
+vector<StatsLogEventWrapper> KernelWakelockPuller::Pull() {
sp<IStatsCompanionService> statsCompanion = StatsService::getStatsCompanionService();
vector<StatsLogEventWrapper> returned_value;
if (statsCompanion != NULL) {
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.h b/cmds/statsd/src/external/KernelWakelockPuller.h
index c12806c..cc8059d 100644
--- a/cmds/statsd/src/external/KernelWakelockPuller.h
+++ b/cmds/statsd/src/external/KernelWakelockPuller.h
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-#ifndef STATSD_KERNELWAKELOCKPULLER_H
-#define STATSD_KERNELWAKELOCKPULLER_H
+#pragma once
#include <utils/String16.h>
-#include "external/StatsPuller.h"
+#include "StatsPuller.h"
namespace android {
namespace os {
@@ -29,11 +28,9 @@
// a number of stats need to be pulled from StatsCompanionService
//
const static int PULL_CODE_KERNEL_WAKELOCKS;
- vector<StatsLogEventWrapper> pull() override;
+ vector<StatsLogEventWrapper> Pull() override;
};
} // namespace statsd
} // namespace os
} // namespace android
-
-#endif // STATSD_KERNELWAKELOCKPULLER_H
diff --git a/cmds/statsd/src/external/PullDataReceiver.h b/cmds/statsd/src/external/PullDataReceiver.h
new file mode 100644
index 0000000..0d505cb
--- /dev/null
+++ b/cmds/statsd/src/external/PullDataReceiver.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <utils/String16.h>
+#include <unordered_map>
+#include <utils/RefBase.h>
+#include "StatsPuller.h"
+#include "logd/LogEvent.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class PullDataReceiver : virtual public RefBase{
+ public:
+ virtual ~PullDataReceiver() {}
+ virtual void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) = 0;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index 6655629..774e7f0 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef STATSD_STATSPULLER_H
-#define STATSD_STATSPULLER_H
+#pragma once
#include <android/os/StatsLogEventWrapper.h>
#include <utils/String16.h>
@@ -32,11 +31,9 @@
public:
virtual ~StatsPuller(){};
- virtual vector<StatsLogEventWrapper> pull() = 0;
+ virtual vector<StatsLogEventWrapper> Pull() = 0;
};
} // namespace statsd
} // namespace os
} // namespace android
-
-#endif // STATSD_STATSPULLER_H
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 7f554d3..f45cb1c 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -18,44 +18,58 @@
#include "Log.h"
#include <android/os/IStatsCompanionService.h>
-#include "KernelWakelockPuller.h"
-#include "StatsService.h"
-#include "external/StatsPullerManager.h"
-#include "logd/LogEvent.h"
#include <cutils/log.h>
#include <algorithm>
+#include <climits>
+#include "KernelWakelockPuller.h"
+#include "StatsPullerManager.h"
+#include "StatsService.h"
+#include "logd/LogEvent.h"
#include <iostream>
-using namespace android;
+using std::string;
+using std::vector;
namespace android {
namespace os {
namespace statsd {
-const int StatsPullerManager::KERNEL_WAKELOCKS = 1;
+const int kernel_wakelock = 1;
+const unordered_map<string, int> StatsPullerManager::kPullCodes({{"KERNEL_WAKELOCK",
+ kernel_wakelock}});
-StatsPullerManager::StatsPullerManager() {
- mStatsPullers.insert(
- {static_cast<int>(KERNEL_WAKELOCKS), std::make_unique<KernelWakelockPuller>()});
+StatsPullerManager::StatsPullerManager()
+ : mCurrentPullingInterval(LONG_MAX), mPullStartTimeMs(get_pull_start_time_ms()) {
+ mPullers.insert({kernel_wakelock, make_unique<KernelWakelockPuller>()});
+ mStatsCompanionService = get_stats_companion_service();
+ if (mStatsCompanionService != nullptr) {
+ mStatsCompanionService->cancelPullingAlarms();
+ } else {
+ VLOG("Failed to update pulling interval");
+ }
}
-vector<std::shared_ptr<LogEvent>> StatsPullerManager::Pull(int pullCode) {
+static const int log_msg_header_size = 28;
+
+vector<shared_ptr<LogEvent>> StatsPullerManager::Pull(int pullCode, uint64_t timestampSec) {
if (DEBUG) ALOGD("Initiating pulling %d", pullCode);
- vector<std::shared_ptr<LogEvent>> ret;
- if (mStatsPullers.find(pullCode) != mStatsPullers.end()) {
- vector<StatsLogEventWrapper> outputs = (mStatsPullers.find(pullCode)->second)->pull();
+ vector<shared_ptr<LogEvent>> ret;
+ auto itr = mPullers.find(pullCode);
+ if (itr != mPullers.end()) {
+ vector<StatsLogEventWrapper> outputs = itr->second->Pull();
for (const StatsLogEventWrapper& it : outputs) {
log_msg tmp;
+ tmp.entry_v1.sec = timestampSec;
+ tmp.entry_v1.nsec = 0;
tmp.entry_v1.len = it.bytes.size();
// Manually set the header size to 28 bytes to match the pushed log events.
- tmp.entry.hdr_size = 28;
+ tmp.entry.hdr_size = log_msg_header_size;
// And set the received bytes starting after the 28 bytes reserved for header.
- std::copy(it.bytes.begin(), it.bytes.end(), tmp.buf + 28);
- std::shared_ptr<LogEvent> evt = std::make_shared<LogEvent>(tmp);
+ copy(it.bytes.begin(), it.bytes.end(), tmp.buf + log_msg_header_size);
+ shared_ptr<LogEvent> evt = make_shared<LogEvent>(tmp);
ret.push_back(evt);
- // ret.emplace_back(tmp);
}
return ret;
} else {
@@ -64,6 +78,112 @@
}
}
+sp<IStatsCompanionService> StatsPullerManager::get_stats_companion_service() {
+ sp<IStatsCompanionService> statsCompanion = nullptr;
+ // Get statscompanion service from service manager
+ const sp<IServiceManager> sm(defaultServiceManager());
+ if (sm != nullptr) {
+ const String16 name("statscompanion");
+ statsCompanion = interface_cast<IStatsCompanionService>(sm->checkService(name));
+ if (statsCompanion == nullptr) {
+ ALOGW("statscompanion service unavailable!");
+ return nullptr;
+ }
+ }
+ return statsCompanion;
+}
+
+StatsPullerManager& StatsPullerManager::GetInstance() {
+ static StatsPullerManager instance;
+ return instance;
+}
+
+int StatsPullerManager::GetPullCode(string atomName) {
+ if (kPullCodes.find(atomName) != kPullCodes.end()) {
+ return kPullCodes.find(atomName)->second;
+ } else {
+ return -1;
+ }
+}
+
+long StatsPullerManager::get_pull_start_time_ms() {
+ // TODO: limit and align pull intervals to 10min boundaries if this turns out to be a problem
+ return time(nullptr) * 1000;
+}
+
+void StatsPullerManager::RegisterReceiver(int pullCode, sp<PullDataReceiver> receiver, long intervalMs) {
+ AutoMutex _l(mReceiversLock);
+ vector<ReceiverInfo>& receivers = mReceivers[pullCode];
+ for (auto it = receivers.begin(); it != receivers.end(); it++) {
+ if (it->receiver.get() == receiver.get()) {
+ VLOG("Receiver already registered of %d", (int)receivers.size());
+ return;
+ }
+ }
+ ReceiverInfo receiverInfo;
+ receiverInfo.receiver = receiver;
+ receiverInfo.timeInfo.first = intervalMs;
+ receivers.push_back(receiverInfo);
+
+ // There is only one alarm for all pulled events. So only set it to the smallest denom.
+ if (intervalMs < mCurrentPullingInterval) {
+ VLOG("Updating pulling interval %ld", intervalMs);
+ mCurrentPullingInterval = intervalMs;
+ if (mStatsCompanionService != nullptr) {
+ mStatsCompanionService->setPullingAlarms(mPullStartTimeMs, mCurrentPullingInterval);
+ } else {
+ VLOG("Failed to update pulling interval");
+ }
+ }
+ VLOG("Puller for pullcode %d registered of %d", pullCode, (int)receivers.size());
+}
+
+void StatsPullerManager::UnRegisterReceiver(int pullCode, sp<PullDataReceiver> receiver) {
+ AutoMutex _l(mReceiversLock);
+ if (mReceivers.find(pullCode) == mReceivers.end()) {
+ VLOG("Unknown pull code or no receivers: %d", pullCode);
+ return;
+ }
+ auto& receivers = mReceivers.find(pullCode)->second;
+ for (auto it = receivers.begin(); it != receivers.end(); it++) {
+ if (receiver.get() == it->receiver.get()) {
+ receivers.erase(it);
+ VLOG("Puller for pullcode %d unregistered of %d", pullCode, (int)receivers.size());
+ return;
+ }
+ }
+}
+
+void StatsPullerManager::OnAlarmFired() {
+ AutoMutex _l(mReceiversLock);
+
+ uint64_t currentTimeMs = time(nullptr) * 1000;
+
+ vector<pair<int, vector<ReceiverInfo*>>> needToPull =
+ vector<pair<int, vector<ReceiverInfo*>>>();
+ for (auto& pair : mReceivers) {
+ vector<ReceiverInfo*> receivers = vector<ReceiverInfo*>();
+ if (pair.second.size() != 0){
+ for(auto& receiverInfo : pair.second) {
+ if (receiverInfo.timeInfo.first + receiverInfo.timeInfo.second > currentTimeMs) {
+ receivers.push_back(&receiverInfo);
+ }
+ }
+ if (receivers.size() > 0) {
+ needToPull.push_back(make_pair(pair.first, receivers));
+ }
+ }
+ }
+
+ for (const auto& pullInfo : needToPull) {
+ const vector<shared_ptr<LogEvent>>& data = Pull(pullInfo.first, currentTimeMs/1000);
+ for(const auto& receiverInfo : pullInfo.second) {
+ receiverInfo->receiver->onDataPulled(data);
+ receiverInfo->timeInfo.second = currentTimeMs;
+ }
+ }
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index e46aec1..e599b69 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -14,38 +14,79 @@
* limitations under the License.
*/
-#ifndef STATSD_STATSPULLERMANAGER_H
-#define STATSD_STATSPULLERMANAGER_H
+#pragma once
+#include <android/os/IStatsCompanionService.h>
+#include <binder/IServiceManager.h>
+#include <utils/RefBase.h>
#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include <string>
#include <unordered_map>
-#include "external/StatsPuller.h"
+#include <vector>
+#include "PullDataReceiver.h"
+#include "StatsPuller.h"
#include "logd/LogEvent.h"
-#include "matchers/matcher_util.h"
namespace android {
namespace os {
namespace statsd {
-const static int KERNEL_WAKELOCKS = 1;
-
class StatsPullerManager : public virtual RefBase {
public:
- // Enums of pulled data types (pullCodes)
- // These values must be kept in sync with com/android/server/stats/StatsCompanionService.java.
- // TODO: pull the constant from stats_events.proto instead
- const static int KERNEL_WAKELOCKS;
- StatsPullerManager();
+ static StatsPullerManager& GetInstance();
+
+ void RegisterReceiver(int pullCode, sp<PullDataReceiver> receiver, long intervalMs);
+
+ void UnRegisterReceiver(int pullCode, sp<PullDataReceiver> receiver);
// We return a vector of shared_ptr since LogEvent's copy constructor is not available.
- vector<std::shared_ptr<LogEvent>> Pull(const int pullCode);
+ vector<std::shared_ptr<LogEvent>> Pull(const int pullCode, const uint64_t timestampSec);
+
+ // Translate metric name to pullCodes.
+ // return -1 if no valid pullCode is found
+ int GetPullCode(std::string metricName);
+
+ void OnAlarmFired();
private:
- std::unordered_map<int, std::unique_ptr<StatsPuller>> mStatsPullers;
+ StatsPullerManager();
+
+ sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+
+ sp<IStatsCompanionService> get_stats_companion_service();
+
+ std::unordered_map<int, std::unique_ptr<StatsPuller>> mPullers;
+
+
+
+ // internal state of a bucket.
+ typedef struct {
+ // pull_interval_sec : last_pull_time_sec
+ std::pair<uint64_t, uint64_t> timeInfo;
+ sp<PullDataReceiver> receiver;
+ } ReceiverInfo;
+
+ std::map<int, std::vector<ReceiverInfo>> mReceivers;
+
+ Mutex mReceiversLock;
+
+ long mCurrentPullingInterval;
+
+ // for value metrics, it is important for the buckets to be aligned to multiple of smallest
+ // bucket size. All pulled metrics start pulling based on this time, so that they can be
+ // correctly attributed to the correct buckets. Pulled data attach a timestamp which is the
+ // request time.
+ const long mPullStartTimeMs;
+
+ long get_pull_start_time_ms();
+
+ LogEvent parse_pulled_data(String16 data);
+
+ static const std::unordered_map<std::string, int> kPullCodes;
};
} // namespace statsd
} // namespace os
-} // namespace android
-
-#endif // STATSD_STATSPULLERMANAGER_H
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index fb992c1..26a82cb 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -14,9 +14,11 @@
* limitations under the License.
*/
+#define DEBUG true // STOPSHIP if true
#include "logd/LogEvent.h"
#include <sstream>
+#include "stats_util.h"
namespace android {
namespace os {
@@ -24,10 +26,11 @@
using std::ostringstream;
using std::string;
+using android::util::ProtoOutputStream;
// We need to keep a copy of the android_log_event_list owned by this instance so that the char*
// for strings is not cleared before we can read them.
-LogEvent::LogEvent(log_msg msg) : mList(msg) {
+LogEvent::LogEvent(log_msg& msg) : mList(msg) {
init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &mList);
}
@@ -203,30 +206,24 @@
return result.str();
}
-void LogEvent::ToProto(EventMetricData* out) const {
- // TODO: Implement this when we have the ProtoOutputStream version.
-
- // set timestamp of the event.
- out->set_timestamp_nanos(mTimestampNs);
-
- // uint64_t token = proto->StartObject(EventMetricData.FIELD);
+void LogEvent::ToProto(ProtoOutputStream& proto) const {
+ long long atomToken = proto.start(TYPE_MESSAGE + mTagId);
const size_t N = mElements.size();
for (size_t i=0; i<N; i++) {
const int key = i + 1;
const android_log_list_element& elem = mElements[i];
if (elem.type == EVENT_TYPE_INT) {
- // proto->Write(key, elem.data.int32);
+ proto.write(TYPE_INT32 + key, elem.data.int32);
} else if (elem.type == EVENT_TYPE_LONG) {
- // proto->Write(key, elem.data.int64);
+ proto.write(TYPE_INT64 + key, (long long)elem.data.int64);
} else if (elem.type == EVENT_TYPE_FLOAT) {
- // proto->Write(key, elem.data.float32);
+ proto.write(TYPE_FLOAT + key, elem.data.float32);
} else if (elem.type == EVENT_TYPE_STRING) {
- // proto->Write(key, elem.data.string);
+ proto.write(TYPE_STRING + key, elem.data.string);
}
}
-
- //proto->EndObject(token);
+ proto.end(atomToken);
}
} // namespace statsd
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 4102675..df75d9f 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -18,9 +18,10 @@
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
-#include <utils/Errors.h>
+#include <android/util/ProtoOutputStream.h>
#include <log/log_event_list.h>
#include <log/log_read.h>
+#include <utils/Errors.h>
#include <memory>
#include <string>
@@ -41,7 +42,7 @@
/**
* Read a LogEvent from a log_msg.
*/
- explicit LogEvent(log_msg msg);
+ explicit LogEvent(log_msg& msg);
/**
* Constructs a LogEvent with the specified tag and creates an android_log_event_list in write
@@ -80,10 +81,9 @@
string ToString() const;
/**
- * Write this object as an EventMetricData proto object.
- * TODO: Use the streaming output generator to do this instead of this proto lite object?
+ * Write this object to a ProtoOutputStream.
*/
- void ToProto(EventMetricData* out) const;
+ void ToProto(android::util::ProtoOutputStream& out) const;
/*
* Get a KeyValuePair proto object.
diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.cpp b/cmds/statsd/src/metrics/CountAnomalyTracker.cpp
index e1c2b8b..7aa748f 100644
--- a/cmds/statsd/src/metrics/CountAnomalyTracker.cpp
+++ b/cmds/statsd/src/metrics/CountAnomalyTracker.cpp
@@ -17,23 +17,22 @@
#define DEBUG true // STOPSHIP if true
#include "Log.h"
-#define VLOG(...) \
- if (DEBUG) ALOGD(__VA_ARGS__);
-
#include "CountAnomalyTracker.h"
+#include <time.h>
+
namespace android {
namespace os {
namespace statsd {
-CountAnomalyTracker::CountAnomalyTracker(size_t numBuckets, int thresholdGt)
- : mNumPastBuckets(numBuckets > 0 ? numBuckets - 1 : 0),
- mPastBuckets(mNumPastBuckets > 0 ? (new int[mNumPastBuckets]) : nullptr),
- mThresholdGt(thresholdGt) {
+CountAnomalyTracker::CountAnomalyTracker(const Alert& alert)
+ : mAlert(alert),
+ mNumPastBuckets(alert.number_of_buckets() > 0 ? alert.number_of_buckets() - 1 : 0),
+ mPastBuckets(mNumPastBuckets > 0 ? (new int[mNumPastBuckets]) : nullptr) {
VLOG("CountAnomalyTracker() called");
- if (numBuckets < 1) {
- ALOGE("Cannot create CountAnomalyTracker with %zu buckets", numBuckets);
+ if (alert.number_of_buckets() < 1) {
+ ALOGE("Cannot create CountAnomalyTracker with %d buckets", alert.number_of_buckets());
}
reset(); // initialization
}
@@ -84,22 +83,45 @@
}
void CountAnomalyTracker::checkAnomaly(int currentCount) {
- // Note that this works even if mNumPastBuckets < 1 (since then
- // mSumPastCounters = 0 so the comparison is based only on currentCount).
+ // Skip the check if in refractory period.
+ if (time(nullptr) < mRefractoryPeriodEndsSec) {
+ VLOG("Skipping anomaly check since within refractory period");
+ return;
+ }
// TODO: Remove these extremely verbose debugging log.
- VLOG("Checking whether %d + %d > %d",
- mSumPastCounters, currentCount, mThresholdGt);
+ VLOG("Checking whether %d + %d > %lld",
+ mSumPastCounters, currentCount, mAlert.trigger_if_sum_gt());
- if (mSumPastCounters + currentCount > mThresholdGt) {
+ // Note that this works even if mNumPastBuckets < 1 (since then
+ // mSumPastCounters = 0 so the comparison is based only on currentCount).
+ if (mAlert.has_trigger_if_sum_gt() &&
+ mSumPastCounters + currentCount > mAlert.trigger_if_sum_gt()) {
declareAnomaly();
}
}
void CountAnomalyTracker::declareAnomaly() {
- // TODO: check that not in refractory period.
- // TODO: Do something.
- ALOGI("An anomaly has occurred!");
+ // TODO(guardrail): Consider guarding against too short refractory periods.
+ time_t currTime = time(nullptr);
+ mRefractoryPeriodEndsSec = currTime + mAlert.refractory_period_secs();
+
+ // TODO: If we had access to the bucket_size_millis, consider calling reset()
+ // if (mAlert.refractory_period_secs() > mNumPastBuckets * bucket_size_millis * 1000).
+
+ if (mAlert.has_incidentd_details()) {
+ const Alert_IncidentdDetails& incident = mAlert.incidentd_details();
+ if (incident.has_alert_name()) {
+ ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
+ incident.alert_name().c_str());
+ } else {
+ // TODO: Can construct a name based on the criteria (and/or relay the criteria).
+ ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
+ }
+ // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
+ } else {
+ ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
+ }
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.h b/cmds/statsd/src/metrics/CountAnomalyTracker.h
index 449dee9..79c47d2a 100644
--- a/cmds/statsd/src/metrics/CountAnomalyTracker.h
+++ b/cmds/statsd/src/metrics/CountAnomalyTracker.h
@@ -17,16 +17,20 @@
#ifndef COUNT_ANOMALY_TRACKER_H
#define COUNT_ANOMALY_TRACKER_H
-#include <stdlib.h>
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
+
#include <memory> // unique_ptr
+#include <stdlib.h>
namespace android {
namespace os {
namespace statsd {
+// TODO: Can probably be used for Count, Value, and Gauge. If so, rename to ValueAnomalyTracker.
+// (caveat: currently, the value cannot be negative. Probably fine for P.)
class CountAnomalyTracker {
public:
- CountAnomalyTracker(size_t numBuckets, int thresholdGt);
+ CountAnomalyTracker(const Alert& alert);
virtual ~CountAnomalyTracker();
@@ -43,6 +47,9 @@
void checkAnomaly(int currentCount);
private:
+ // statsd_config.proto Alert message that defines this tracker.
+ const Alert mAlert;
+
// Number of past buckets. One less than the total number of buckets needed
// for the anomaly detection (since the current bucket is not in the past).
const size_t mNumPastBuckets;
@@ -59,8 +66,9 @@
// Index of the oldest bucket (i.e. the next bucket to be overwritten).
size_t mOldestBucketIndex = 0;
- // If mSumPastCounters + currentCount > mThresholdGt --> Anomaly!
- const int mThresholdGt;
+ // Timestamp that the refractory period (if this anomaly was declared) ends, in seconds.
+ // If an anomaly was never declared, set to 0.
+ time_t mRefractoryPeriodEndsSec = 0;
void declareAnomaly();
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 1f07914..9f8558d 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -21,7 +21,6 @@
#include "CountMetricProducer.h"
#include "stats_util.h"
-#include <cutils/log.h>
#include <limits.h>
#include <stdlib.h>
@@ -39,9 +38,7 @@
const sp<ConditionWizard>& wizard)
// TODO: Pass in the start time from MetricsManager, instead of calling time() here.
: MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
- mMetric(metric),
- // TODO: read mAnomalyTracker parameters from config file.
- mAnomalyTracker(6, 10) {
+ mMetric(metric) {
// TODO: evaluate initial conditions. and set mConditionMet.
if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
@@ -49,6 +46,17 @@
mBucketSizeNs = LLONG_MAX;
}
+ mAnomalyTrackers.reserve(metric.alerts_size());
+ for (int i = 0; i < metric.alerts_size(); i++) {
+ const Alert& alert = metric.alerts(i);
+ if (alert.trigger_if_sum_gt() > 0 && alert.number_of_buckets() > 0) {
+ mAnomalyTrackers.push_back(std::make_unique<CountAnomalyTracker>(alert));
+ } else {
+ ALOGW("Ignoring invalid count metric alert: threshold=%lld num_buckets= %d",
+ alert.trigger_if_sum_gt(), alert.number_of_buckets());
+ }
+ }
+
// TODO: use UidMap if uid->pkg_name is required
mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
@@ -85,7 +93,7 @@
}
}
-void CountMetricProducer::onSlicedConditionMayChange() {
+void CountMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
}
@@ -119,57 +127,28 @@
// TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
}
-void CountMetricProducer::onConditionChanged(const bool conditionMet) {
+void CountMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
mCondition = conditionMet;
}
-void CountMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
+void CountMetricProducer::onMatchedLogEventInternal(
+ const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const map<string, HashableDimensionKey>& conditionKey, bool condition,
+ const LogEvent& event, bool scheduledPull) {
uint64_t eventTimeNs = event.GetTimestampNs();
- // this is old event, maybe statsd restarted?
- if (eventTimeNs < mStartTimeNs) {
- return;
- }
flushCounterIfNeeded(eventTimeNs);
- if (mConditionSliced) {
- map<string, HashableDimensionKey> conditionKeys;
- for (const auto& link : mConditionLinks) {
- VLOG("Condition link key_in_main size %d", link.key_in_main_size());
- HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
- conditionKeys[link.condition()] = conditionKey;
- }
- if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) {
- VLOG("metric %lld sliced condition not met", mMetric.metric_id());
- return;
- }
- } else {
- if (!mCondition) {
- VLOG("metric %lld condition not met", mMetric.metric_id());
- return;
- }
+ if (condition == false) {
+ return;
}
- HashableDimensionKey hashableKey;
-
- if (mDimension.size() > 0) {
- vector<KeyValuePair> key = getDimensionKey(event, mDimension);
- hashableKey = getHashableKey(key);
- // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport
- // expects vector<KeyValuePair>.
- if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) {
- mDimensionKeyMap[hashableKey] = key;
- }
- } else {
- hashableKey = DEFAULT_DIMENSION_KEY;
- }
-
- auto it = mCurrentSlicedCounter.find(hashableKey);
+ auto it = mCurrentSlicedCounter.find(eventKey);
if (it == mCurrentSlicedCounter.end()) {
// create a counter for the new key
- mCurrentSlicedCounter[hashableKey] = 1;
+ mCurrentSlicedCounter[eventKey] = 1;
} else {
// increment the existing value
@@ -177,8 +156,13 @@
count++;
}
- VLOG("metric %lld %s->%d", mMetric.metric_id(), hashableKey.c_str(),
- mCurrentSlicedCounter[hashableKey]);
+ // TODO: Re-add anomaly detection (similar to):
+ // for (auto& tracker : mAnomalyTrackers) {
+ // tracker->checkAnomaly(mCounter);
+ // }
+
+ VLOG("metric %lld %s->%d", mMetric.metric_id(), eventKey.c_str(),
+ mCurrentSlicedCounter[eventKey]);
}
// When a new matched event comes in, we check if event falls into the current
@@ -205,6 +189,11 @@
counter.second);
}
+ // TODO: Re-add anomaly detection (similar to):
+ // for (auto& tracker : mAnomalyTrackers) {
+ // tracker->addPastBucket(mCounter, numBucketsForward);
+ //}
+
// Reset counters
mCurrentSlicedCounter.clear();
@@ -213,6 +202,13 @@
(long long)mCurrentBucketStartTimeNs);
}
+size_t CountMetricProducer::byteSize() {
+// TODO: return actual proto size when ProtoOutputStream is ready for use for
+// CountMetricsProducer.
+// return mProto->size();
+ return 0;
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index f0d6025..80e80d9 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -41,30 +41,38 @@
virtual ~CountMetricProducer();
- void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override;
-
- void onConditionChanged(const bool conditionMet) override;
+ void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
void finish() override;
StatsLogReport onDumpReport() override;
- void onSlicedConditionMayChange() override;
+ void onSlicedConditionMayChange(const uint64_t eventTime) override;
+
+ size_t byteSize() override;
// TODO: Implement this later.
virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+ // TODO: Implement this later.
+ virtual void notifyAppRemoved(const string& apk, const int uid) override{};
+
+protected:
+ void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const std::map<std::string, HashableDimensionKey>& conditionKey,
+ bool condition, const LogEvent& event,
+ bool scheduledPull) override;
private:
const CountMetric mMetric;
- CountAnomalyTracker mAnomalyTracker;
-
// Save the past buckets and we can clear when the StatsLogReport is dumped.
std::unordered_map<HashableDimensionKey, std::vector<CountBucketInfo>> mPastBuckets;
// The current bucket.
std::unordered_map<HashableDimensionKey, int> mCurrentSlicedCounter;
+ vector<unique_ptr<CountAnomalyTracker>> mAnomalyTrackers;
+
void flushCounterIfNeeded(const uint64_t newEventTime);
};
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index aa597f4..340f503 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -15,11 +15,11 @@
*/
#define DEBUG true
-#include "DurationMetricProducer.h"
+
#include "Log.h"
+#include "DurationMetricProducer.h"
#include "stats_util.h"
-#include <cutils/log.h>
#include <limits.h>
#include <stdlib.h>
@@ -34,13 +34,15 @@
DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
const int conditionIndex, const size_t startIndex,
const size_t stopIndex, const size_t stopAllIndex,
- const sp<ConditionWizard>& wizard)
+ const sp<ConditionWizard>& wizard,
+ const vector<KeyMatcher>& internalDimension)
// TODO: Pass in the start time from MetricsManager, instead of calling time() here.
: MetricProducer(time(nullptr) * NANO_SECONDS_IN_A_SECOND, conditionIndex, wizard),
mMetric(metric),
mStartIndex(startIndex),
mStopIndex(stopIndex),
- mStopAllIndex(stopAllIndex) {
+ mStopAllIndex(stopAllIndex),
+ mInternalDimension(internalDimension) {
// TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
// them in the base class, because the proto generated CountMetric, and DurationMetric are
// not related. Maybe we should add a template in the future??
@@ -67,34 +69,43 @@
VLOG("~DurationMetric() called");
}
+unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
+ vector<DurationBucketInfo>& bucket) {
+ switch (mMetric.type()) {
+ case DurationMetric_AggregationType_DURATION_SUM:
+ return make_unique<OringDurationTracker>(mWizard, mConditionTrackerIndex,
+ mCurrentBucketStartTimeNs, mBucketSizeNs,
+ bucket);
+ case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
+ return make_unique<MaxDurationTracker>(mWizard, mConditionTrackerIndex,
+ mCurrentBucketStartTimeNs, mBucketSizeNs,
+ bucket);
+ }
+}
+
void DurationMetricProducer::finish() {
// TODO: write the StatsLogReport to dropbox using
// DropboxWriter.
}
-void DurationMetricProducer::onSlicedConditionMayChange() {
+void DurationMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
// Now for each of the on-going event, check if the condition has changed for them.
+ flushIfNeeded(eventTime);
for (auto& pair : mCurrentSlicedDuration) {
- VLOG("Metric %lld current %s state: %d", mMetric.metric_id(), pair.first.c_str(),
- pair.second.state);
- if (pair.second.state == kStopped) {
- continue;
- }
- bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
- ConditionState::kTrue;
- VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
- noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+ pair.second->onSlicedConditionMayChange(eventTime);
}
}
-void DurationMetricProducer::onConditionChanged(const bool conditionMet) {
+void DurationMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
mCondition = conditionMet;
// TODO: need to populate the condition change time from the event which triggers the condition
// change, instead of using current time.
+
+ flushIfNeeded(eventTime);
for (auto& pair : mCurrentSlicedDuration) {
- noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+ pair.second->onConditionChanged(conditionMet, eventTime);
}
}
@@ -107,7 +118,7 @@
}
for (const auto& bucket : buckets) {
data->add_bucket_info()->CopyFrom(bucket);
- VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
+ VLOG("\t bucket [%lld - %lld] duration(ns): %lld", bucket.start_bucket_nanos(),
bucket.end_bucket_nanos(), bucket.duration_nanos());
}
}
@@ -120,7 +131,7 @@
// Dump current bucket if it's stale.
// If current bucket is still on-going, don't force dump current bucket.
// In finish(), We can force dump current bucket.
- flushDurationIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+ flushIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
report.set_end_report_nanos(mCurrentBucketStartTimeNs);
StatsLogReport_DurationMetricDataWrapper* wrapper = report.mutable_duration_metrics();
@@ -137,246 +148,57 @@
return report;
};
-void DurationMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
- if (event.GetTimestampNs() < mStartTimeNs) {
- return;
- }
-
- flushDurationIfNeeded(event.GetTimestampNs());
-
- if (matcherIndex == mStopAllIndex) {
- noteStopAll(event.GetTimestampNs());
- return;
- }
-
- HashableDimensionKey hashableKey;
- if (mDimension.size() > 0) {
- // hook up sliced counter with AnomalyMonitor.
- vector<KeyValuePair> key = getDimensionKey(event, mDimension);
- hashableKey = getHashableKey(key);
- // Add the HashableDimensionKey->DimensionKey to the map, because StatsLogReport expects
- // vector<KeyValuePair>.
- if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) {
- mDimensionKeyMap[hashableKey] = key;
- }
- } else {
- hashableKey = DEFAULT_DIMENSION_KEY;
- }
-
- if (mCurrentSlicedDuration.find(hashableKey) == mCurrentSlicedDuration.end() &&
- mConditionSliced) {
- // add the durationInfo for the current bucket.
- auto& durationInfo = mCurrentSlicedDuration[hashableKey];
- auto& conditionKeys = durationInfo.conditionKeys;
- // get and cache the keys for query condition.
- for (const auto& link : mConditionLinks) {
- HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
- conditionKeys[link.condition()] = conditionKey;
- }
- }
-
- bool conditionMet;
- if (mConditionSliced) {
- const auto& conditionKeys = mCurrentSlicedDuration[hashableKey].conditionKeys;
- conditionMet =
- mWizard->query(mConditionTrackerIndex, conditionKeys) == ConditionState::kTrue;
- } else {
- conditionMet = mCondition;
- }
-
- if (matcherIndex == mStartIndex) {
- VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
- conditionMet);
- noteStart(hashableKey, conditionMet, event.GetTimestampNs());
- } else if (matcherIndex == mStopIndex) {
- VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
- conditionMet);
- noteStop(hashableKey, event.GetTimestampNs());
- }
-}
-
-void DurationMetricProducer::noteConditionChanged(const HashableDimensionKey& key,
- const bool conditionMet,
- const uint64_t eventTime) {
- flushDurationIfNeeded(eventTime);
-
- auto it = mCurrentSlicedDuration.find(key);
- if (it == mCurrentSlicedDuration.end()) {
- return;
- }
-
- switch (it->second.state) {
- case kStarted:
- // if condition becomes false, kStarted -> kPaused. Record the current duration.
- if (!conditionMet) {
- it->second.state = DurationState::kPaused;
- it->second.lastDuration =
- updateDuration(it->second.lastDuration,
- eventTime - it->second.lastStartTime, mMetric.type());
- VLOG("Metric %lld Key: %s Paused because condition is false ", mMetric.metric_id(),
- key.c_str());
- }
- break;
- case kStopped:
- // nothing to do if it's stopped.
- break;
- case kPaused:
- // if condition becomes true, kPaused -> kStarted. and the start time is the condition
- // change time.
- if (conditionMet) {
- it->second.state = DurationState::kStarted;
- it->second.lastStartTime = eventTime;
- VLOG("Metric %lld Key: %s Paused->Started", mMetric.metric_id(), key.c_str());
- }
- break;
- }
-}
-
-void DurationMetricProducer::noteStart(const HashableDimensionKey& key, const bool conditionMet,
- const uint64_t eventTime) {
- // this will add an empty bucket for this key if it didn't exist before.
- DurationInfo& duration = mCurrentSlicedDuration[key];
-
- switch (duration.state) {
- case kStarted:
- // It's safe to do nothing here. even if condition is not true, it means we are about
- // to receive the condition change event.
- break;
- case kPaused:
- // Safe to do nothing here. kPaused is waiting for the condition change.
- break;
- case kStopped:
- if (!conditionMet) {
- // event started, but we need to wait for the condition to become true.
- duration.state = DurationState::kPaused;
- break;
- }
- duration.state = DurationState::kStarted;
- duration.lastStartTime = eventTime;
- break;
- }
-}
-
-void DurationMetricProducer::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
- if (mCurrentSlicedDuration.find(key) == mCurrentSlicedDuration.end()) {
- // we didn't see a start event before. do nothing.
- return;
- }
- DurationInfo& duration = mCurrentSlicedDuration[key];
-
- switch (duration.state) {
- case DurationState::kStopped:
- // already stopped, do nothing.
- break;
- case DurationState::kStarted: {
- duration.state = DurationState::kStopped;
- int64_t durationTime = eventTime - duration.lastStartTime;
- VLOG("Metric %lld, key %s, Stop %lld %lld %lld", mMetric.metric_id(), key.c_str(),
- (long long)duration.lastStartTime, (long long)eventTime, (long long)durationTime);
- duration.lastDuration =
- updateDuration(duration.lastDuration, durationTime, mMetric.type());
- VLOG(" record duration: %lld ", (long long)duration.lastDuration);
- break;
- }
- case DurationState::kPaused: {
- duration.state = DurationState::kStopped;
- break;
- }
- }
-}
-
-int64_t DurationMetricProducer::updateDuration(const int64_t lastDuration,
- const int64_t durationTime,
- const DurationMetric_AggregationType type) {
- int64_t result = lastDuration;
- switch (type) {
- case DurationMetric_AggregationType_DURATION_SUM:
- result += durationTime;
- break;
- case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
- if (lastDuration < durationTime) {
- result = durationTime;
- }
- break;
- case DurationMetric_AggregationType_DURATION_MIN_SPARSE:
- if (lastDuration > durationTime) {
- result = durationTime;
- }
- break;
- }
- return result;
-}
-
-void DurationMetricProducer::noteStopAll(const uint64_t eventTime) {
- for (auto& duration : mCurrentSlicedDuration) {
- noteStop(duration.first, eventTime);
- }
-}
-
-// When a new matched event comes in, we check if event falls into the current
-// bucket. If not, flush the old counter to past buckets and initialize the current buckt.
-void DurationMetricProducer::flushDurationIfNeeded(const uint64_t eventTime) {
+void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) {
if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
return;
}
- // adjust the bucket start time
- int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
-
- DurationBucketInfo info;
- uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
- info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
- info.set_end_bucket_nanos(endTime);
-
- uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
- mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
- VLOG("Metric %lld: new bucket start time: %lld", mMetric.metric_id(),
- (long long)mCurrentBucketStartTimeNs);
-
+ VLOG("flushing...........");
for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end(); ++it) {
- int64_t finalDuration = it->second.lastDuration;
- if (it->second.state == kStarted) {
- // the event is still on-going, duration needs to be updated.
- int64_t durationTime = endTime - it->second.lastStartTime;
- finalDuration = updateDuration(it->second.lastDuration, durationTime, mMetric.type());
- }
-
- VLOG(" final duration for last bucket: %lld", (long long)finalDuration);
-
- // Don't record empty bucket.
- if (finalDuration != 0) {
- info.set_duration_nanos(finalDuration);
- // it will auto create new vector of CountbucketInfo if the key is not found.
- auto& bucketList = mPastBuckets[it->first];
- bucketList.push_back(info);
- }
-
- // if the event is still on-going, add the buckets between previous bucket and now. Because
- // the event has been going on across all the buckets in between.
- // |prev_bucket|...|..|...|now_bucket|
- if (it->second.state == kStarted) {
- for (int i = 1; i < numBucketsForward; i++) {
- DurationBucketInfo info;
- info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
- info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
- info.set_duration_nanos(mBucketSizeNs);
- auto& bucketList = mPastBuckets[it->first];
- bucketList.push_back(info);
- VLOG(" add filling bucket with duration %lld", (long long)mBucketSizeNs);
- }
- }
-
- if (it->second.state == DurationState::kStopped) {
- // No need to keep buckets for events that were stopped before. If the event starts
- // again, we will add it back.
+ if (it->second->flushIfNeeded(eventTime)) {
+ VLOG("erase bucket for key %s", it->first.c_str());
mCurrentSlicedDuration.erase(it);
- } else {
- // for kPaused, and kStarted event, we will keep the buckets, and reset the start time
- // and duration.
- it->second.lastStartTime = mCurrentBucketStartTimeNs;
- it->second.lastDuration = 0;
}
}
+
+ int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+ mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
+}
+
+void DurationMetricProducer::onMatchedLogEventInternal(
+ const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const map<string, HashableDimensionKey>& conditionKeys, bool condition,
+ const LogEvent& event, bool scheduledPull) {
+ flushIfNeeded(event.GetTimestampNs());
+
+ if (matcherIndex == mStopAllIndex) {
+ for (auto& pair : mCurrentSlicedDuration) {
+ pair.second->noteStopAll(event.GetTimestampNs());
+ }
+ return;
+ }
+
+ HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
+
+ if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
+ mCurrentSlicedDuration[eventKey] = createDurationTracker(mPastBuckets[eventKey]);
+ }
+
+ auto it = mCurrentSlicedDuration.find(eventKey);
+
+ if (matcherIndex == mStartIndex) {
+ it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys);
+
+ } else if (matcherIndex == mStopIndex) {
+ it->second->noteStop(atomKey, event.GetTimestampNs());
+ }
+}
+
+size_t DurationMetricProducer::byteSize() {
+// TODO: return actual proto size when ProtoOutputStream is ready for use for
+// DurationMetricsProducer.
+// return mProto->size();
+ return 0;
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 44c3254..febf25d 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -22,6 +22,9 @@
#include "../condition/ConditionTracker.h"
#include "../matchers/matcher_util.h"
#include "MetricProducer.h"
+#include "duration_helper/DurationTracker.h"
+#include "duration_helper/MaxDurationTracker.h"
+#include "duration_helper/OringDurationTracker.h"
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "stats_util.h"
@@ -32,47 +35,35 @@
namespace os {
namespace statsd {
-enum DurationState {
- kStopped = 0, // The event is stopped.
- kStarted = 1, // The event is on going.
- kPaused = 2, // The event is started, but condition is false, clock is paused. When condition
- // turns to true, kPaused will become kStarted.
-};
-
-// Hold duration information for current on-going bucket.
-struct DurationInfo {
- DurationState state;
- // most recent start time.
- int64_t lastStartTime;
- // existing duration in current bucket. Eventually, the duration will be aggregated in
- // the way specified in AggregateType (Sum, Max, or Min).
- int64_t lastDuration;
- // cache the HashableDimensionKeys we need to query the condition for this duration event.
- std::map<string, HashableDimensionKey> conditionKeys;
-
- DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
-};
-
class DurationMetricProducer : public MetricProducer {
public:
DurationMetricProducer(const DurationMetric& durationMetric, const int conditionIndex,
const size_t startIndex, const size_t stopIndex,
- const size_t stopAllIndex, const sp<ConditionWizard>& wizard);
+ const size_t stopAllIndex, const sp<ConditionWizard>& wizard,
+ const vector<KeyMatcher>& internalDimension);
virtual ~DurationMetricProducer();
- void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override;
-
- void onConditionChanged(const bool conditionMet) override;
+ void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
void finish() override;
StatsLogReport onDumpReport() override;
- void onSlicedConditionMayChange() override;
+ void onSlicedConditionMayChange(const uint64_t eventTime) override;
+
+ size_t byteSize() override;
// TODO: Implement this later.
virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+ // TODO: Implement this later.
+ virtual void notifyAppRemoved(const string& apk, const int uid) override{};
+
+protected:
+ void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const std::map<std::string, HashableDimensionKey>& conditionKeys,
+ bool condition, const LogEvent& event,
+ bool scheduledPull) override;
private:
const DurationMetric mMetric;
@@ -86,26 +77,21 @@
// Index of the SimpleLogEntryMatcher which defines the stop all for all dimensions.
const size_t mStopAllIndex;
+ // The dimension from the atom predicate. e.g., uid, wakelock name.
+ const vector<KeyMatcher> mInternalDimension;
+
// Save the past buckets and we can clear when the StatsLogReport is dumped.
std::unordered_map<HashableDimensionKey, std::vector<DurationBucketInfo>> mPastBuckets;
// The current bucket.
- std::unordered_map<HashableDimensionKey, DurationInfo> mCurrentSlicedDuration;
+ std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
+ mCurrentSlicedDuration;
void flushDurationIfNeeded(const uint64_t newEventTime);
- void noteStart(const HashableDimensionKey& key, const bool conditionMet,
- const uint64_t eventTime);
+ std::unique_ptr<DurationTracker> createDurationTracker(std::vector<DurationBucketInfo>& bucket);
- void noteStop(const HashableDimensionKey& key, const uint64_t eventTime);
-
- void noteStopAll(const uint64_t eventTime);
-
- static int64_t updateDuration(const int64_t lastDuration, const int64_t durationTime,
- const DurationMetric_AggregationType type);
-
- void noteConditionChanged(const HashableDimensionKey& key, const bool conditionMet,
- const uint64_t eventTime);
+ void flushIfNeeded(uint64_t timestamp);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
new file mode 100644
index 0000000..d714179
--- /dev/null
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include "EventMetricProducer.h"
+#include "stats_util.h"
+
+#include <limits.h>
+#include <stdlib.h>
+
+using android::util::ProtoOutputStream;
+using std::map;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// for StatsLogReport
+const int FIELD_ID_METRIC_ID = 1;
+const int FIELD_ID_START_REPORT_NANOS = 2;
+const int FIELD_ID_END_REPORT_NANOS = 2;
+const int FIELD_ID_EVENT_METRICS = 4;
+// for EventMetricData
+const int FIELD_ID_TIMESTAMP_NANOS = 1;
+const int FIELD_ID_STATS_EVENTS = 2;
+// for CountMetricDataWrapper
+const int FIELD_ID_DATA = 1;
+
+EventMetricProducer::EventMetricProducer(const EventMetric& metric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard)
+ // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
+ : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
+ mMetric(metric) {
+ if (metric.links().size() > 0) {
+ mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+ metric.links().end());
+ mConditionSliced = true;
+ }
+
+ startNewProtoOutputStream(mStartTimeNs);
+
+ VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+ (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+EventMetricProducer::~EventMetricProducer() {
+ VLOG("~EventMetricProducer() called");
+}
+
+void EventMetricProducer::startNewProtoOutputStream(long long startTime) {
+ mProto = std::make_unique<ProtoOutputStream>();
+ // TODO: We need to auto-generate the field IDs for StatsLogReport, EventMetricData,
+ // and StatsEvent.
+ mProto->write(TYPE_INT32 + FIELD_ID_METRIC_ID, mMetric.metric_id());
+ mProto->write(TYPE_INT64 + FIELD_ID_START_REPORT_NANOS, startTime);
+ mProtoToken = mProto->start(TYPE_MESSAGE + FIELD_ID_EVENT_METRICS);
+}
+
+void EventMetricProducer::finish() {
+}
+
+void EventMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
+}
+
+StatsLogReport EventMetricProducer::onDumpReport() {
+ long long endTime = time(nullptr) * NANO_SECONDS_IN_A_SECOND;
+ mProto->end(mProtoToken);
+ mProto->write(TYPE_INT64 + FIELD_ID_END_REPORT_NANOS, endTime);
+
+ size_t bufferSize = mProto->size();
+ VLOG("metric %lld dump report now... proto size: %zu ", mMetric.metric_id(), bufferSize);
+ std::unique_ptr<uint8_t[]> buffer(new uint8_t[bufferSize]);
+ size_t pos = 0;
+ auto it = mProto->data();
+ while (it.readBuffer() != NULL) {
+ size_t toRead = it.currentToRead();
+ std::memcpy(&buffer[pos], it.readBuffer(), toRead);
+ pos += toRead;
+ it.rp()->move(toRead);
+ }
+
+ startNewProtoOutputStream(endTime);
+
+ // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
+ // return std::move(buffer);
+ return StatsLogReport();
+}
+
+void EventMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
+ VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
+ mCondition = conditionMet;
+}
+
+void EventMetricProducer::onMatchedLogEventInternal(
+ const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+ const LogEvent& event, bool scheduledPull) {
+
+ if (!condition) {
+ return;
+ }
+
+ long long wrapperToken = mProto->start(TYPE_MESSAGE + FIELD_ID_DATA);
+ mProto->write(TYPE_INT64 + FIELD_ID_TIMESTAMP_NANOS, (long long)event.GetTimestampNs());
+ long long eventToken = mProto->start(TYPE_MESSAGE + FIELD_ID_STATS_EVENTS);
+ event.ToProto(*mProto);
+ mProto->end(eventToken);
+ mProto->end(wrapperToken);
+}
+
+size_t EventMetricProducer::byteSize() {
+ return mProto->size();
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
new file mode 100644
index 0000000..7dd0e38
--- /dev/null
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef EVENT_METRIC_PRODUCER_H
+#define EVENT_METRIC_PRODUCER_H
+
+#include <unordered_map>
+
+#include <android/util/ProtoOutputStream.h>
+#include "../condition/ConditionTracker.h"
+#include "../matchers/matcher_util.h"
+#include "MetricProducer.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class EventMetricProducer : public MetricProducer {
+public:
+ // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
+ EventMetricProducer(const EventMetric& eventMetric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard);
+
+ virtual ~EventMetricProducer();
+
+ void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const std::map<std::string, HashableDimensionKey>& conditionKey,
+ bool condition, const LogEvent& event, bool scheduledPull) override;
+
+ void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
+
+ void finish() override;
+
+ StatsLogReport onDumpReport() override;
+
+ void onSlicedConditionMayChange(const uint64_t eventTime) override;
+
+ size_t byteSize() override;
+
+ // TODO: Implement this later.
+ virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+ // TODO: Implement this later.
+ virtual void notifyAppRemoved(const string& apk, const int uid) override{};
+
+private:
+ const EventMetric mMetric;
+
+ std::unique_ptr<android::util::ProtoOutputStream> mProto;
+
+ long long mProtoToken;
+
+ void startNewProtoOutputStream(long long timestamp);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#endif // EVENT_METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
new file mode 100644
index 0000000..535f4a2
--- /dev/null
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "MetricProducer.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::map;
+
+void MetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event,
+ bool scheduledPull) {
+ uint64_t eventTimeNs = event.GetTimestampNs();
+ // this is old event, maybe statsd restarted?
+ if (eventTimeNs < mStartTimeNs) {
+ return;
+ }
+
+ HashableDimensionKey eventKey;
+
+ if (mDimension.size() > 0) {
+ vector<KeyValuePair> key = getDimensionKey(event, mDimension);
+ eventKey = getHashableKey(key);
+ // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport
+ // expects vector<KeyValuePair>.
+ if (mDimensionKeyMap.find(eventKey) == mDimensionKeyMap.end()) {
+ mDimensionKeyMap[eventKey] = key;
+ }
+ } else {
+ eventKey = DEFAULT_DIMENSION_KEY;
+ }
+
+ bool condition;
+
+ map<string, HashableDimensionKey> conditionKeys;
+ if (mConditionSliced) {
+ for (const auto& link : mConditionLinks) {
+ HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
+ conditionKeys[link.condition()] = conditionKey;
+ }
+ if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) {
+ condition = false;
+ } else {
+ condition = true;
+ }
+ } else {
+ condition = mCondition;
+ }
+
+ onMatchedLogEventInternal(matcherIndex, eventKey, conditionKeys, condition, event,
+ scheduledPull);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index afaab648..3b117ec 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -48,11 +48,11 @@
virtual ~MetricProducer(){};
// Consume the parsed stats log entry that already matched the "what" of the metric.
- virtual void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) = 0;
+ void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event, bool scheduledPull);
- virtual void onConditionChanged(const bool condition) = 0;
+ virtual void onConditionChanged(const bool condition, const uint64_t eventTime) = 0;
- virtual void onSlicedConditionMayChange() = 0;
+ virtual void onSlicedConditionMayChange(const uint64_t eventTime) = 0;
// This is called when the metric collecting is done, e.g., when there is a new configuration
// coming. MetricProducer should do the clean up, and dump existing data to dropbox.
@@ -64,6 +64,8 @@
return mConditionSliced;
};
+ virtual size_t byteSize() = 0;
+
protected:
const uint64_t mStartTimeNs;
@@ -86,6 +88,26 @@
std::unordered_map<HashableDimensionKey, std::vector<KeyValuePair>> mDimensionKeyMap;
std::vector<EventConditionLink> mConditionLinks;
+
+ /*
+ * Individual metrics can implement their own business logic here. All pre-processing is done.
+ *
+ * [matcherIndex]: the index of the matcher which matched this event. This is interesting to
+ * DurationMetric, because it has start/stop/stop_all 3 matchers.
+ * [eventKey]: the extracted dimension key for the final output. if the metric doesn't have
+ * dimensions, it will be DEFAULT_DIMENSION_KEY
+ * [conditionKey]: the keys of conditions which should be used to query the condition for this
+ * target event (from EventConditionLink). This is passed to individual metrics
+ * because DurationMetric needs it to be cached.
+ * [condition]: whether condition is met. If condition is sliced, this is the result coming from
+ * query with ConditionWizard; If condition is not sliced, this is the
+ * nonSlicedCondition.
+ * [event]: the log event, just in case the metric needs its data, e.g., EventMetric.
+ */
+ virtual void onMatchedLogEventInternal(
+ const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+ const LogEvent& event, bool scheduledPull) = 0;
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index c19d462..521fcc3 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -43,7 +43,7 @@
}
MetricsManager::~MetricsManager() {
- VLOG("~MetricManager()");
+ VLOG("~MetricsManager()");
}
bool MetricsManager::isConfigValid() const {
@@ -74,6 +74,7 @@
}
int tagId = event.GetTagId();
+ uint64_t eventTime = event.GetTimestampNs();
if (mTagIds.find(tagId) == mTagIds.end()) {
// not interesting...
return;
@@ -124,13 +125,14 @@
// metric cares about non sliced condition, and it's changed.
// Push the new condition to it directly.
if (!mAllMetricProducers[metricIndex]->isConditionSliced() && changedCache[i]) {
- mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i]);
+ mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i],
+ eventTime);
// metric cares about sliced conditions, and it may have changed. Send
// notification, and the metric can query the sliced conditions that are
// interesting to it.
} else if (mAllMetricProducers[metricIndex]->isConditionSliced() &&
slicedChangedCache[i]) {
- mAllMetricProducers[metricIndex]->onSlicedConditionMayChange();
+ mAllMetricProducers[metricIndex]->onSlicedConditionMayChange(eventTime);
}
}
}
@@ -143,13 +145,23 @@
if (pair != mTrackerToMetricMap.end()) {
auto& metricList = pair->second;
for (const int metricIndex : metricList) {
- mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event);
+ // pushed metrics are never scheduled pulls
+ mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event, false);
}
}
}
}
}
+// Returns the total byte size of all metrics managed by a single config source.
+size_t MetricsManager::byteSize() {
+ size_t totalSize = 0;
+ for (auto metricProducer : mAllMetricProducers) {
+ totalSize += metricProducer->byteSize();
+ }
+ return totalSize;
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 56f57d3..44cd637 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -46,16 +46,18 @@
// Config source owner can call onDumpReport() to get all the metrics collected.
std::vector<StatsLogReport> onDumpReport();
+ size_t byteSize();
+
private:
// All event tags that are interesting to my metrics.
std::set<int> mTagIds;
// We only store the sp of LogMatchingTracker, MetricProducer, and ConditionTracker in
- // MetricManager. There are relationship between them, and the relationship are denoted by index
- // instead of pointers. The reasons for this are: (1) the relationship between them are
- // complicated, store index instead of pointers reduce the risk of A holds B's sp, and B holds
- // A's sp. (2) When we evaluate matcher results, or condition results, we can quickly get the
- // related results from a cache using the index.
+ // MetricsManager. There are relationships between them, and the relationships are denoted by
+ // index instead of pointers. The reasons for this are: (1) the relationship between them are
+ // complicated, so storing index instead of pointers reduces the risk that A holds B's sp, and B
+ // holds A's sp. (2) When we evaluate matcher results, or condition results, we can quickly get
+ // the related results from a cache using the index.
// Hold all the log entry matchers from the config.
std::vector<sp<LogMatchingTracker>> mAllLogEntryMatchers;
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
new file mode 100644
index 0000000..cb6166d
--- /dev/null
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include "ValueMetricProducer.h"
+
+#include <cutils/log.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using std::map;
+using std::unordered_map;
+using std::list;
+using std::make_shared;
+using std::shared_ptr;
+using std::unique_ptr;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
+ValueMetricProducer::ValueMetricProducer(const ValueMetric& metric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard)
+ : MetricProducer((time(nullptr) / 600 * 600 * NANO_SECONDS_IN_A_SECOND), conditionIndex,
+ wizard),
+ mMetric(metric),
+ mPullCode(mStatsPullerManager.GetPullCode(mMetric.what())) {
+ // TODO: valuemetric for pushed events may need unlimited bucket length
+ mBucketSizeNs = mMetric.bucket().bucket_size_millis() * 1000 * 1000;
+
+ mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+
+ if (metric.links().size() > 0) {
+ mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+ metric.links().end());
+ mConditionSliced = true;
+ }
+
+ if (!metric.has_condition() && mPullCode != -1) {
+ mStatsPullerManager.RegisterReceiver(mPullCode, this, metric.bucket().bucket_size_millis());
+ }
+
+ VLOG("value metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+ (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+ValueMetricProducer::~ValueMetricProducer() {
+ VLOG("~ValueMetricProducer() called");
+}
+
+void ValueMetricProducer::finish() {
+ // TODO: write the StatsLogReport to dropbox using
+ // DropboxWriter.
+}
+
+static void addSlicedCounterToReport(StatsLogReport_ValueMetricDataWrapper& wrapper,
+ const vector<KeyValuePair>& key,
+ const vector<ValueBucketInfo>& buckets) {
+ ValueMetricData* data = wrapper.add_data();
+ for (const auto& kv : key) {
+ data->add_dimension()->CopyFrom(kv);
+ }
+ for (const auto& bucket : buckets) {
+ data->add_bucket_info()->CopyFrom(bucket);
+ VLOG("\t bucket [%lld - %lld] value: %lld", bucket.start_bucket_nanos(),
+ bucket.end_bucket_nanos(), bucket.value());
+ }
+}
+
+void ValueMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
+ VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+}
+
+StatsLogReport ValueMetricProducer::onDumpReport() {
+ VLOG("metric %lld dump report now...", mMetric.metric_id());
+
+ StatsLogReport report;
+ report.set_metric_id(mMetric.metric_id());
+ report.set_start_report_nanos(mStartTimeNs);
+
+ // Dump current bucket if it's stale.
+ // If current bucket is still on-going, don't force dump current bucket.
+ // In finish(), We can force dump current bucket.
+ // flush_if_needed(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+ report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+ StatsLogReport_ValueMetricDataWrapper* wrapper = report.mutable_value_metrics();
+
+ for (const auto& pair : mPastBuckets) {
+ const HashableDimensionKey& hashableKey = pair.first;
+ auto it = mDimensionKeyMap.find(hashableKey);
+ if (it == mDimensionKeyMap.end()) {
+ ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+ continue;
+ }
+
+ VLOG(" dimension key %s", hashableKey.c_str());
+ addSlicedCounterToReport(*wrapper, it->second, pair.second);
+ }
+ return report;
+ // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
+}
+
+void ValueMetricProducer::onConditionChanged(const bool condition, const uint64_t eventTime) {
+ mCondition = condition;
+
+ if (mPullCode != -1) {
+ vector<shared_ptr<LogEvent>> allData = mStatsPullerManager.Pull(mPullCode, eventTime);
+ if (mCondition == true) {
+ mStatsPullerManager.RegisterReceiver(mPullCode, this,
+ mMetric.bucket().bucket_size_millis());
+ } else if (mCondition == ConditionState::kFalse) {
+ mStatsPullerManager.UnRegisterReceiver(mPullCode, this);
+ }
+ if (allData.size() == 0) {
+ return;
+ }
+ AutoMutex _l(mLock);
+ if (allData.size() == 0) {
+ return;
+ }
+ for (const auto& data : allData) {
+ onMatchedLogEvent(0, *data, false);
+ }
+ flush_if_needed(eventTime);
+ }
+ return;
+}
+
+void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
+ if (mCondition == ConditionState::kTrue || !mMetric.has_condition()) {
+ AutoMutex _l(mLock);
+ if (allData.size() == 0) {
+ return;
+ }
+ uint64_t eventTime = allData.at(0)->GetTimestampNs();
+ for (const auto& data : allData) {
+ onMatchedLogEvent(0, *data, true);
+ }
+ flush_if_needed(eventTime);
+ }
+}
+
+void ValueMetricProducer::onMatchedLogEventInternal(
+ const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const map<string, HashableDimensionKey>& conditionKey, bool condition,
+ const LogEvent& event, bool scheduledPull) {
+ uint64_t eventTimeNs = event.GetTimestampNs();
+ if (eventTimeNs < mCurrentBucketStartTimeNs) {
+ VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
+ (long long)mCurrentBucketStartTimeNs);
+ return;
+ }
+
+ Interval& interval = mCurrentSlicedBucket[eventKey];
+
+ long value = get_value(event);
+
+ if (scheduledPull) {
+ if (interval.raw.size() > 0) {
+ interval.raw.back().second = value;
+ } else {
+ interval.raw.push_back(std::make_pair(value, value));
+ }
+ mNextSlicedBucket[eventKey].raw[0].first = value;
+ } else {
+ if (mCondition == ConditionState::kTrue) {
+ interval.raw.push_back(std::make_pair(value, 0));
+ } else {
+ if (interval.raw.size() != 0) {
+ interval.raw.back().second = value;
+ }
+ }
+ }
+ if (mPullCode == -1) {
+ flush_if_needed(eventTimeNs);
+ }
+}
+
+long ValueMetricProducer::get_value(const LogEvent& event) {
+ status_t err = NO_ERROR;
+ long val = event.GetLong(mMetric.value_field(), &err);
+ if (err == NO_ERROR) {
+ return val;
+ } else {
+ VLOG("Can't find value in message.");
+ return 0;
+ }
+}
+
+void ValueMetricProducer::flush_if_needed(const uint64_t eventTimeNs) {
+ if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
+ VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
+ (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
+ return;
+ }
+
+ VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs,
+ (int)mCurrentSlicedBucket.size());
+ ValueBucketInfo info;
+ info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+ info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
+
+ for (const auto& slice : mCurrentSlicedBucket) {
+ long value = 0;
+ for (const auto& pair : slice.second.raw) {
+ value += pair.second - pair.first;
+ }
+ info.set_value(value);
+ VLOG(" %s, %ld", slice.first.c_str(), value);
+ // it will auto create new vector of ValuebucketInfo if the key is not found.
+ auto& bucketList = mPastBuckets[slice.first];
+ bucketList.push_back(info);
+ }
+
+ // Reset counters
+ mCurrentSlicedBucket.swap(mNextSlicedBucket);
+ mNextSlicedBucket.clear();
+ int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+ if (numBucketsForward >1) {
+ VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
+ }
+ mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+ VLOG("metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+ (long long)mCurrentBucketStartTimeNs);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
new file mode 100644
index 0000000..4f17913
--- /dev/null
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utils/threads.h>
+#include <list>
+#include "../condition/ConditionTracker.h"
+#include "../external/PullDataReceiver.h"
+#include "../external/StatsPullerManager.h"
+#include "CountAnomalyTracker.h"
+#include "MetricProducer.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
+public:
+ ValueMetricProducer(const ValueMetric& valueMetric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard);
+
+ virtual ~ValueMetricProducer();
+
+ void onConditionChanged(const bool condition, const uint64_t eventTime) override;
+
+ void finish() override;
+
+ StatsLogReport onDumpReport() override;
+
+ void onSlicedConditionMayChange(const uint64_t eventTime);
+
+ void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
+ // TODO: Implement this later.
+ size_t byteSize() override{return 0;};
+
+ // TODO: Implement this later.
+ virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+ // TODO: Implement this later.
+ virtual void notifyAppRemoved(const string& apk, const int uid) override{};
+
+protected:
+ void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const std::map<std::string, HashableDimensionKey>& conditionKey,
+ bool condition, const LogEvent& event,
+ bool scheduledPull) override;
+
+private:
+ const ValueMetric mMetric;
+
+ StatsPullerManager& mStatsPullerManager = StatsPullerManager::GetInstance();
+
+ Mutex mLock;
+
+ const int mPullCode;
+
+ // internal state of a bucket.
+ typedef struct {
+ std::vector<std::pair<long, long>> raw;
+ } Interval;
+
+ std::unordered_map<HashableDimensionKey, Interval> mCurrentSlicedBucket;
+ // If condition is true and pulling on schedule, the previous bucket value needs to be carried
+ // over to the next bucket.
+ std::unordered_map<HashableDimensionKey, Interval> mNextSlicedBucket;
+
+ // Save the past buckets and we can clear when the StatsLogReport is dumped.
+ std::unordered_map<HashableDimensionKey, std::vector<ValueBucketInfo>> mPastBuckets;
+
+ long get_value(const LogEvent& event);
+
+ void flush_if_needed(const uint64_t eventTimeNs);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
new file mode 100644
index 0000000..0d0d9a4
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DURATION_TRACKER_H
+#define DURATION_TRACKER_H
+
+#include "condition/ConditionWizard.h"
+#include "stats_util.h"
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+enum DurationState {
+ kStopped = 0, // The event is stopped.
+ kStarted = 1, // The event is on going.
+ kPaused = 2, // The event is started, but condition is false, clock is paused. When condition
+ // turns to true, kPaused will become kStarted.
+};
+
+// Hold duration information for one atom level duration in current on-going bucket.
+struct DurationInfo {
+ DurationState state;
+ // most recent start time.
+ int64_t lastStartTime;
+ // existing duration in current bucket.
+ int64_t lastDuration;
+ // TODO: Optimize the way we track sliced condition in duration metrics.
+ // cache the HashableDimensionKeys we need to query the condition for this duration event.
+ ConditionKey conditionKeys;
+
+ DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
+};
+
+class DurationTracker {
+public:
+ DurationTracker(sp<ConditionWizard> wizard, int conditionIndex, uint64_t currentBucketStartNs,
+ uint64_t bucketSizeNs, std::vector<DurationBucketInfo>& bucket)
+ : mWizard(wizard),
+ mConditionTrackerIndex(conditionIndex),
+ mCurrentBucketStartTimeNs(currentBucketStartNs),
+ mBucketSizeNs(bucketSizeNs),
+ mBucket(bucket),
+ mDuration(0){};
+ virtual ~DurationTracker(){};
+ virtual void noteStart(const HashableDimensionKey& key, bool condition,
+ const uint64_t eventTime, const ConditionKey& conditionKey) = 0;
+ virtual void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) = 0;
+ virtual void noteStopAll(const uint64_t eventTime) = 0;
+ virtual void onSlicedConditionMayChange(const uint64_t timestamp) = 0;
+ virtual void onConditionChanged(bool condition, const uint64_t timestamp) = 0;
+ // Flush stale buckets if needed, and return true if the tracker has no on-going duration
+ // events, so that the owner can safely remove the tracker.
+ virtual bool flushIfNeeded(uint64_t timestampNs) = 0;
+
+protected:
+ sp<ConditionWizard> mWizard;
+
+ int mConditionTrackerIndex;
+
+ uint64_t mCurrentBucketStartTimeNs;
+
+ int64_t mBucketSizeNs;
+
+ std::vector<DurationBucketInfo>& mBucket; // where to write output
+
+ int64_t mDuration; // current recorded duration result
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif // DURATION_TRACKER_H
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
new file mode 100644
index 0000000..856ca9d
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true
+
+#include "Log.h"
+#include "MaxDurationTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+MaxDurationTracker::MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+ uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+ std::vector<DurationBucketInfo>& bucket)
+ : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket) {
+}
+
+void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
+ const uint64_t eventTime, const ConditionKey& conditionKey) {
+ // this will construct a new DurationInfo if this key didn't exist.
+ DurationInfo& duration = mInfos[key];
+ duration.conditionKeys = conditionKey;
+ VLOG("MaxDuration: key %s start condition %d", key.c_str(), condition);
+
+ switch (duration.state) {
+ case kStarted:
+ // The same event is already started. Because we are not counting nesting, so ignore.
+ break;
+ case kPaused:
+ // Safe to do nothing here. Paused means started but condition is false.
+ break;
+ case kStopped:
+ if (!condition) {
+ // event started, but we need to wait for the condition to become true.
+ duration.state = DurationState::kPaused;
+ } else {
+ duration.state = DurationState::kStarted;
+ duration.lastStartTime = eventTime;
+ }
+ break;
+ }
+}
+
+void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
+ VLOG("MaxDuration: key %s stop", key.c_str());
+ if (mInfos.find(key) == mInfos.end()) {
+ // we didn't see a start event before. do nothing.
+ return;
+ }
+ DurationInfo& duration = mInfos[key];
+
+ switch (duration.state) {
+ case DurationState::kStopped:
+ // already stopped, do nothing.
+ break;
+ case DurationState::kStarted: {
+ duration.state = DurationState::kStopped;
+ int64_t durationTime = eventTime - duration.lastStartTime;
+ VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(), (long long)duration.lastStartTime,
+ (long long)eventTime, (long long)durationTime);
+ duration.lastDuration = duration.lastDuration + durationTime;
+ VLOG(" record duration: %lld ", (long long)duration.lastDuration);
+ break;
+ }
+ case DurationState::kPaused: {
+ duration.state = DurationState::kStopped;
+ break;
+ }
+ }
+
+ if (duration.lastDuration > mDuration) {
+ mDuration = duration.lastDuration;
+ VLOG("Max: new max duration: %lld", (long long)mDuration);
+ }
+ // Once an atom duration ends, we erase it. Next time, if we see another atom event with the
+ // same name, they are still considered as different atom durations.
+ mInfos.erase(key);
+}
+void MaxDurationTracker::noteStopAll(const uint64_t eventTime) {
+ for (auto& pair : mInfos) {
+ noteStop(pair.first, eventTime);
+ }
+}
+
+bool MaxDurationTracker::flushIfNeeded(uint64_t eventTime) {
+ if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+ return false;
+ }
+
+ VLOG("MaxDurationTracker flushing.....");
+
+ // adjust the bucket start time
+ int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+
+ DurationBucketInfo info;
+ uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+ info.set_end_bucket_nanos(endTime);
+
+ uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
+ mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
+
+ bool hasOnGoingStartedEvent = false; // a kStarted event last across bucket boundaries.
+ bool hasPendingEvent =
+ false; // has either a kStarted or kPaused event across bucket boundaries
+ // meaning we need to carry them over to the new bucket.
+ for (auto it = mInfos.begin(); it != mInfos.end(); ++it) {
+ int64_t finalDuration = it->second.lastDuration;
+ if (it->second.state == kStarted) {
+ // the event is still on-going, duration needs to be updated.
+ // |..lastDurationTime_recorded...last_start -----|bucket_end. We need to record the
+ // duration between lastStartTime and bucketEnd.
+ int64_t durationTime = endTime - it->second.lastStartTime;
+
+ finalDuration += durationTime;
+ VLOG(" unrecorded %lld -> %lld", (long long)(durationTime), (long long)finalDuration);
+ // if the event is still on-going, we need to fill the buckets between prev_bucket and
+ // now_bucket. |prev_bucket|...|..|...|now_bucket|
+ hasOnGoingStartedEvent = true;
+ }
+
+ if (finalDuration > mDuration) {
+ mDuration = finalDuration;
+ }
+
+ if (it->second.state == DurationState::kStopped) {
+ // No need to keep buckets for events that were stopped before.
+ mInfos.erase(it);
+ } else {
+ hasPendingEvent = true;
+ // for kPaused, and kStarted event, we will keep track of them, and reset the start time
+ // and duration.
+ it->second.lastStartTime = mCurrentBucketStartTimeNs;
+ it->second.lastDuration = 0;
+ }
+ }
+
+ if (mDuration != 0) {
+ info.set_duration_nanos(mDuration);
+ mBucket.push_back(info);
+ VLOG(" final duration for last bucket: %lld", (long long)mDuration);
+ }
+
+ mDuration = 0;
+ if (hasOnGoingStartedEvent) {
+ for (int i = 1; i < numBucketsForward; i++) {
+ DurationBucketInfo info;
+ info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
+ info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
+ info.set_duration_nanos(mBucketSizeNs);
+ mBucket.push_back(info);
+ VLOG(" filling gap bucket with duration %lld", (long long)mBucketSizeNs);
+ }
+ }
+ // If this tracker has no pending events, tell owner to remove.
+ return !hasPendingEvent;
+}
+
+void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
+ // VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+ // Now for each of the on-going event, check if the condition has changed for them.
+ for (auto& pair : mInfos) {
+ if (pair.second.state == kStopped) {
+ continue;
+ }
+ bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
+ ConditionState::kTrue;
+ VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
+ noteConditionChanged(pair.first, conditionMet, timestamp);
+ }
+}
+
+void MaxDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
+ for (auto& pair : mInfos) {
+ noteConditionChanged(pair.first, condition, timestamp);
+ }
+}
+
+void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
+ const uint64_t timestamp) {
+ auto it = mInfos.find(key);
+ if (it == mInfos.end()) {
+ return;
+ }
+
+ switch (it->second.state) {
+ case kStarted:
+ // if condition becomes false, kStarted -> kPaused. Record the current duration.
+ if (!conditionMet) {
+ it->second.state = DurationState::kPaused;
+ it->second.lastDuration += (timestamp - it->second.lastStartTime);
+
+ VLOG("MaxDurationTracker Key: %s Started->Paused ", key.c_str());
+ }
+ break;
+ case kStopped:
+ // nothing to do if it's stopped.
+ break;
+ case kPaused:
+ // if condition becomes true, kPaused -> kStarted. and the start time is the condition
+ // change time.
+ if (conditionMet) {
+ it->second.state = DurationState::kStarted;
+ it->second.lastStartTime = timestamp;
+ VLOG("MaxDurationTracker Key: %s Paused->Started", key.c_str());
+ }
+ break;
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
new file mode 100644
index 0000000..c74d070
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MAX_DURATION_TRACKER_H
+#define MAX_DURATION_TRACKER_H
+
+#include "DurationTracker.h"
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Tracks a pool of atom durations, and output the max duration for each bucket.
+// To get max duration, we need to keep track of each individual durations, and compare them when
+// they stop or bucket expires.
+class MaxDurationTracker : public DurationTracker {
+public:
+ MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+ uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+ std::vector<DurationBucketInfo>& bucket);
+ void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
+ const ConditionKey& conditionKey) override;
+ void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) override;
+ void noteStopAll(const uint64_t eventTime) override;
+ bool flushIfNeeded(uint64_t timestampNs) override;
+ void onSlicedConditionMayChange(const uint64_t timestamp) override;
+ void onConditionChanged(bool condition, const uint64_t timestamp) override;
+
+private:
+ std::map<HashableDimensionKey, DurationInfo> mInfos;
+
+ void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
+ const uint64_t timestamp);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif // MAX_DURATION_TRACKER_H
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
new file mode 100644
index 0000000..e045fb4
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define DEBUG true
+#include "Log.h"
+#include "OringDurationTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+OringDurationTracker::OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+ uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+ std::vector<DurationBucketInfo>& bucket)
+ : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket),
+ mStarted(),
+ mPaused() {
+ mLastStartTime = 0;
+}
+
+void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
+ const uint64_t eventTime, const ConditionKey& conditionKey) {
+ if (condition) {
+ if (mStarted.size() == 0) {
+ mLastStartTime = eventTime;
+ VLOG("record first start....");
+ }
+ mStarted.insert(key);
+ } else {
+ mPaused.insert(key);
+ }
+
+ if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
+ mConditionKeyMap[key] = conditionKey;
+ }
+
+ VLOG("Oring: %s start, condition %d", key.c_str(), condition);
+}
+
+void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp) {
+ VLOG("Oring: %s stop", key.c_str());
+ auto it = mStarted.find(key);
+ if (it != mStarted.end()) {
+ mStarted.erase(it);
+ if (mStarted.empty()) {
+ mDuration += (timestamp - mLastStartTime);
+ VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
+ (long long)mDuration);
+ }
+ }
+
+ mPaused.erase(key);
+ mConditionKeyMap.erase(key);
+}
+void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
+ if (!mStarted.empty()) {
+ mDuration += (timestamp - mLastStartTime);
+ VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
+ (long long)mDuration);
+ }
+
+ mStarted.clear();
+ mPaused.clear();
+ mConditionKeyMap.clear();
+}
+
+bool OringDurationTracker::flushIfNeeded(uint64_t eventTime) {
+ if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+ return false;
+ }
+ VLOG("OringDurationTracker Flushing.............");
+ // adjust the bucket start time
+ int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+ DurationBucketInfo info;
+ uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+ info.set_end_bucket_nanos(endTime);
+
+ uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
+ mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
+
+ if (mStarted.size() > 0) {
+ mDuration += (endTime - mLastStartTime);
+ }
+ if (mDuration != 0) {
+ info.set_duration_nanos(mDuration);
+ // it will auto create new vector of CountbucketInfo if the key is not found.
+ mBucket.push_back(info);
+ VLOG(" duration: %lld", (long long)mDuration);
+ }
+
+ if (mStarted.size() > 0) {
+ for (int i = 1; i < numBucketsForward; i++) {
+ DurationBucketInfo info;
+ info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
+ info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
+ info.set_duration_nanos(mBucketSizeNs);
+ mBucket.push_back(info);
+ VLOG(" add filling bucket with duration %lld", (long long)mBucketSizeNs);
+ }
+ }
+ mLastStartTime = mCurrentBucketStartTimeNs;
+ mDuration = 0;
+
+ // if all stopped, then tell owner it's safe to remove this tracker.
+ return mStarted.empty() && mPaused.empty();
+}
+
+void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
+ vector<HashableDimensionKey> startedToPaused;
+ vector<HashableDimensionKey> pausedToStarted;
+ if (!mStarted.empty()) {
+ for (auto it = mStarted.begin(); it != mStarted.end();) {
+ auto key = *it;
+ if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
+ VLOG("Key %s dont have condition key", key.c_str());
+ ++it;
+ continue;
+ }
+ if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) !=
+ ConditionState::kTrue) {
+ it = mStarted.erase(it);
+ startedToPaused.push_back(key);
+ VLOG("Key %s started -> paused", key.c_str());
+ } else {
+ ++it;
+ }
+ }
+
+ if (mStarted.empty()) {
+ mDuration += (timestamp - mLastStartTime);
+ VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
+ (long long)mDuration);
+ }
+ }
+
+ if (!mPaused.empty()) {
+ for (auto it = mPaused.begin(); it != mPaused.end();) {
+ auto key = *it;
+ if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
+ VLOG("Key %s dont have condition key", key.c_str());
+ ++it;
+ continue;
+ }
+ if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) ==
+ ConditionState::kTrue) {
+ it = mPaused.erase(it);
+ pausedToStarted.push_back(key);
+ VLOG("Key %s paused -> started", key.c_str());
+ } else {
+ ++it;
+ }
+ }
+
+ if (mStarted.empty() && pausedToStarted.size() > 0) {
+ mLastStartTime = timestamp;
+ }
+ }
+
+ mStarted.insert(pausedToStarted.begin(), pausedToStarted.end());
+ mPaused.insert(startedToPaused.begin(), startedToPaused.end());
+}
+
+void OringDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
+ if (condition) {
+ if (!mPaused.empty()) {
+ VLOG("Condition true, all started");
+ if (mStarted.empty()) {
+ mLastStartTime = timestamp;
+ }
+ mStarted.insert(mPaused.begin(), mPaused.end());
+ }
+ } else {
+ if (!mStarted.empty()) {
+ VLOG("Condition false, all paused");
+ mDuration += (timestamp - mLastStartTime);
+ mPaused.insert(mStarted.begin(), mStarted.end());
+ }
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
new file mode 100644
index 0000000..5425251
--- /dev/null
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ORING_DURATION_TRACKER_H
+#define ORING_DURATION_TRACKER_H
+
+#include "DurationTracker.h"
+
+#include <set>
+namespace android {
+namespace os {
+namespace statsd {
+
+// Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted.
+class OringDurationTracker : public DurationTracker {
+public:
+ OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+ uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+ std::vector<DurationBucketInfo>& bucket);
+ void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
+ const ConditionKey& conditionKey) override;
+ void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) override;
+ void noteStopAll(const uint64_t eventTime) override;
+ void onSlicedConditionMayChange(const uint64_t timestamp) override;
+ void onConditionChanged(bool condition, const uint64_t timestamp) override;
+ bool flushIfNeeded(uint64_t timestampNs) override;
+
+private:
+ // We don't need to keep track of individual durations. The information that's needed is:
+ // 1) which keys are started. We record the first start time.
+ // 2) which keys are paused (started but condition was false)
+ // 3) whenever a key stops, we remove it from the started set. And if the set becomes empty,
+ // it means everything has stopped, we then record the end time.
+ std::set<HashableDimensionKey> mStarted;
+ std::set<HashableDimensionKey> mPaused;
+ int64_t mLastStartTime;
+ std::map<HashableDimensionKey, ConditionKey> mConditionKeyMap;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif // ORING_DURATION_TRACKER_H
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 23071aa..3d4036e 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -16,10 +16,13 @@
#include "../condition/CombinationConditionTracker.h"
#include "../condition/SimpleConditionTracker.h"
+#include "../external/StatsPullerManager.h"
#include "../matchers/CombinationLogMatchingTracker.h"
#include "../matchers/SimpleLogMatchingTracker.h"
#include "CountMetricProducer.h"
#include "DurationMetricProducer.h"
+#include "EventMetricProducer.h"
+#include "ValueMetricProducer.h"
#include "stats_util.h"
using std::set;
@@ -31,13 +34,58 @@
namespace os {
namespace statsd {
-int getTrackerIndex(const string& name, const unordered_map<string, int>& logTrackerMap) {
- auto logTrackerIt = logTrackerMap.find(name);
+bool handleMetricWithLogTrackers(const string what, const int metricIndex,
+ const bool usedForDimension,
+ const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
+ const unordered_map<string, int>& logTrackerMap,
+ unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ int& logTrackerIndex) {
+ auto logTrackerIt = logTrackerMap.find(what);
if (logTrackerIt == logTrackerMap.end()) {
- ALOGW("cannot find the LogEventMatcher %s in config", name.c_str());
- return MATCHER_NOT_FOUND;
+ ALOGW("cannot find the LogEntryMatcher %s in config", what.c_str());
+ return false;
}
- return logTrackerIt->second;
+ if (usedForDimension && allLogEntryMatchers[logTrackerIt->second]->getTagIds().size() > 1) {
+ ALOGE("LogEntryMatcher %s has more than one tag ids. When a metric has dimension, the "
+ "\"what\" can only about one atom type.",
+ what.c_str());
+ return false;
+ }
+ logTrackerIndex = logTrackerIt->second;
+ auto& metric_list = trackerToMetricMap[logTrackerIndex];
+ metric_list.push_back(metricIndex);
+ return true;
+}
+
+bool handleMetricWithConditions(
+ const string condition, const int metricIndex,
+ const unordered_map<string, int>& conditionTrackerMap,
+ const ::google::protobuf::RepeatedPtrField<::android::os::statsd::EventConditionLink>&
+ links,
+ vector<sp<ConditionTracker>>& allConditionTrackers, int& conditionIndex,
+ unordered_map<int, std::vector<int>>& conditionToMetricMap) {
+ auto condition_it = conditionTrackerMap.find(condition);
+ if (condition_it == conditionTrackerMap.end()) {
+ ALOGW("cannot find the Condition %s in the config", condition.c_str());
+ return false;
+ }
+
+ for (const auto& link : links) {
+ auto it = conditionTrackerMap.find(link.condition());
+ if (it == conditionTrackerMap.end()) {
+ ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+ return false;
+ }
+ allConditionTrackers[condition_it->second]->setSliced(true);
+ allConditionTrackers[it->second]->setSliced(true);
+ // TODO: We need to verify the link is valid.
+ }
+ conditionIndex = condition_it->second;
+
+ // will create new vector if not exist before.
+ auto& metricList = conditionToMetricMap[condition_it->second];
+ metricList.push_back(metricIndex);
+ return true;
}
bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap,
@@ -137,13 +185,16 @@
bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& logTrackerMap,
const unordered_map<string, int>& conditionTrackerMap,
+ const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
unordered_map<int, std::vector<int>>& conditionToMetricMap,
unordered_map<int, std::vector<int>>& trackerToMetricMap) {
sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
- const int allMetricsCount = config.count_metric_size() + config.duration_metric_size();
+ const int allMetricsCount =
+ config.count_metric_size() + config.duration_metric_size() + config.event_metric_size();
allMetricProducers.reserve(allMetricsCount);
+ StatsPullerManager& statsPullerManager = StatsPullerManager::GetInstance();
// Build MetricProducers for each metric defined in config.
// (1) build CountMetricProducer
@@ -155,108 +206,138 @@
}
int metricIndex = allMetricProducers.size();
-
- auto logTrackerIt = logTrackerMap.find(metric.what());
- if (logTrackerIt == logTrackerMap.end()) {
- ALOGW("cannot find the LogEntryMatcher %s in config", metric.what().c_str());
+ int trackerIndex;
+ if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
+ allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
+ trackerIndex)) {
return false;
}
- int logTrackerIndex = logTrackerIt->second;
- auto& metric_list = trackerToMetricMap[logTrackerIndex];
- metric_list.push_back(metricIndex);
- sp<MetricProducer> countProducer;
-
+ int conditionIndex = -1;
if (metric.has_condition()) {
- auto condition_it = conditionTrackerMap.find(metric.condition());
- if (condition_it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", metric.condition().c_str());
- return false;
- }
-
- for (const auto& link : metric.links()) {
- auto it = conditionTrackerMap.find(link.condition());
- if (it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
- return false;
- }
- allConditionTrackers[condition_it->second]->setSliced(true);
- allConditionTrackers[it->second]->setSliced(true);
- allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>(
- link.key_in_condition().begin(), link.key_in_condition().end()));
- }
-
- countProducer = new CountMetricProducer(metric, condition_it->second, wizard);
- // will create new vector if not exist before.
- auto& metricList = conditionToMetricMap[condition_it->second];
- metricList.push_back(metricIndex);
- } else {
- countProducer = new CountMetricProducer(metric, -1 /*no condition*/, wizard);
+ handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap);
}
+
+ sp<MetricProducer> countProducer = new CountMetricProducer(metric, conditionIndex, wizard);
allMetricProducers.push_back(countProducer);
}
for (int i = 0; i < config.duration_metric_size(); i++) {
int metricIndex = allMetricProducers.size();
- const DurationMetric metric = config.duration_metric(i);
- if (!metric.has_start()) {
- ALOGW("cannot find start in DurationMetric %lld", metric.metric_id());
+ const DurationMetric& metric = config.duration_metric(i);
+
+ auto what_it = conditionTrackerMap.find(metric.what());
+ if (what_it == conditionTrackerMap.end()) {
+ ALOGE("DurationMetric's \"what\" is invalid");
return false;
}
- int trackerIndices[] = {-1, -1, -1};
- trackerIndices[0] = getTrackerIndex(metric.start(), logTrackerMap);
+ const Condition& durationWhat = config.condition(what_it->second);
- if (metric.has_stop()) {
- trackerIndices[1] = getTrackerIndex(metric.stop(), logTrackerMap);
+ if (durationWhat.contents_case() != Condition::ContentsCase::kSimpleCondition) {
+ ALOGE("DurationMetric's \"what\" must be a simple condition");
+ return false;
}
- if (metric.has_stop_all()) {
- trackerIndices[2] = getTrackerIndex(metric.stop_all(), logTrackerMap);
+ const auto& simpleCondition = durationWhat.simple_condition();
+
+ int trackerIndices[3] = {-1, -1, -1};
+ if (!simpleCondition.has_start() ||
+ !handleMetricWithLogTrackers(simpleCondition.start(), metricIndex,
+ metric.dimension_size() > 0, allLogEntryMatchers,
+ logTrackerMap, trackerToMetricMap, trackerIndices[0])) {
+ ALOGE("Duration metrics must specify a valid the start event matcher");
+ return false;
}
- for (const int& index : trackerIndices) {
- if (index == MATCHER_NOT_FOUND) {
- return false;
- }
- if (index >= 0) {
- auto& metric_list = trackerToMetricMap[index];
- metric_list.push_back(metricIndex);
- }
+ if (simpleCondition.has_stop() &&
+ !handleMetricWithLogTrackers(simpleCondition.stop(), metricIndex,
+ metric.dimension_size() > 0, allLogEntryMatchers,
+ logTrackerMap, trackerToMetricMap, trackerIndices[1])) {
+ return false;
}
+ if (simpleCondition.has_stop_all() &&
+ !handleMetricWithLogTrackers(simpleCondition.stop_all(), metricIndex,
+ metric.dimension_size() > 0, allLogEntryMatchers,
+ logTrackerMap, trackerToMetricMap, trackerIndices[2])) {
+ return false;
+ }
+
+ vector<KeyMatcher> internalDimension;
+ internalDimension.insert(internalDimension.begin(), simpleCondition.dimension().begin(),
+ simpleCondition.dimension().end());
+
int conditionIndex = -1;
if (metric.has_predicate()) {
- auto condition_it = conditionTrackerMap.find(metric.predicate());
- if (condition_it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", metric.predicate().c_str());
- return false;
- }
- conditionIndex = condition_it->second;
-
- for (const auto& link : metric.links()) {
- auto it = conditionTrackerMap.find(link.condition());
- if (it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
- return false;
- }
- allConditionTrackers[condition_it->second]->setSliced(true);
- allConditionTrackers[it->second]->setSliced(true);
- allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>(
- link.key_in_condition().begin(), link.key_in_condition().end()));
- }
-
- auto& metricList = conditionToMetricMap[conditionIndex];
- metricList.push_back(metricIndex);
+ handleMetricWithConditions(metric.predicate(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap);
}
- sp<MetricProducer> durationMetric =
- new DurationMetricProducer(metric, conditionIndex, trackerIndices[0],
- trackerIndices[1], trackerIndices[2], wizard);
+ sp<MetricProducer> durationMetric = new DurationMetricProducer(
+ metric, conditionIndex, trackerIndices[0], trackerIndices[1], trackerIndices[2],
+ wizard, internalDimension);
allMetricProducers.push_back(durationMetric);
}
+
+ for (int i = 0; i < config.event_metric_size(); i++) {
+ int metricIndex = allMetricProducers.size();
+ const EventMetric& metric = config.event_metric(i);
+ if (!metric.has_metric_id() || !metric.has_what()) {
+ ALOGW("cannot find the metric id or what in config");
+ return false;
+ }
+ int trackerIndex;
+ if (!handleMetricWithLogTrackers(metric.what(), metricIndex, false, allLogEntryMatchers,
+ logTrackerMap, trackerToMetricMap, trackerIndex)) {
+ return false;
+ }
+
+ int conditionIndex = -1;
+ if (metric.has_condition()) {
+ handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap);
+ }
+
+ sp<MetricProducer> eventMetric = new EventMetricProducer(metric, conditionIndex, wizard);
+
+ allMetricProducers.push_back(eventMetric);
+ }
+
+ // value metrics
+ for (int i = 0; i < config.value_metric_size(); i++) {
+ const ValueMetric& metric = config.value_metric(i);
+ if (!metric.has_what()) {
+ ALOGW("cannot find what in ValueMetric %lld", metric.metric_id());
+ return false;
+ }
+
+ int pullCode = statsPullerManager.GetPullCode(metric.what());
+ if (pullCode == -1) {
+ ALOGW("cannot find %s in pulled metrics", metric.what().c_str());
+ return false;
+ }
+
+ sp<MetricProducer> valueProducer;
+ auto condition_it = conditionTrackerMap.find(metric.condition());
+ if (condition_it == conditionTrackerMap.end()) {
+ ALOGW("cannot find the Condition %s in the config", metric.condition().c_str());
+ return false;
+ }
+ int metricIndex = allMetricProducers.size();
+ valueProducer = new ValueMetricProducer(metric, condition_it->second, wizard);
+ // will create new vector if not exist before.
+ auto& metricList = conditionToMetricMap[condition_it->second];
+ metricList.push_back(metricIndex);
+
+ allMetricProducers.push_back(valueProducer);
+ }
return true;
}
@@ -281,8 +362,9 @@
return false;
}
- if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
- allMetricProducers, conditionToMetricMap, trackerToMetricMap)) {
+ if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allLogEntryMatchers,
+ allConditionTrackers, allMetricProducers, conditionToMetricMap,
+ trackerToMetricMap)) {
ALOGE("initMetricProducers failed");
return false;
}
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 38149a6..e089d065 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -21,8 +21,8 @@
#include <vector>
#include "../condition/ConditionTracker.h"
+#include "../external/StatsPullerManager.h"
#include "../matchers/LogMatchingTracker.h"
-#include "CountMetricProducer.h"
namespace android {
namespace os {
@@ -76,12 +76,13 @@
const StatsdConfig& config, const std::unordered_map<std::string, int>& logTrackerMap,
const std::unordered_map<std::string, int>& conditionTrackerMap,
const std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks,
+ const vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
vector<sp<ConditionTracker>>& allConditionTrackers,
std::vector<sp<MetricProducer>>& allMetricProducers,
std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToMetricMap);
-// Initialize MetricManager from StatsdConfig.
+// Initialize MetricsManager from StatsdConfig.
// Parameters are the members of MetricsManager. See MetricsManager for declaration.
bool initStatsdConfig(const StatsdConfig& config, std::set<int>& allTagIds,
std::vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
@@ -91,8 +92,6 @@
std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
-int getTrackerIndex(const std::string& name, const std::unordered_map<string, int>& logTrackerMap);
-
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/packages/PackageInfoListener.h b/cmds/statsd/src/packages/PackageInfoListener.h
index 8b948de..13e776f 100644
--- a/cmds/statsd/src/packages/PackageInfoListener.h
+++ b/cmds/statsd/src/packages/PackageInfoListener.h
@@ -29,6 +29,9 @@
// Uid map will notify this listener that the app with apk name and uid has been upgraded to
// the specified version.
virtual void notifyAppUpgrade(const std::string& apk, const int uid, const int version) = 0;
+
+ // Notify interested listeners that the given apk and uid combination no longer exits.
+ virtual void notifyAppRemoved(const std::string& apk, const int uid) = 0;
};
} // namespace statsd
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index f4621ee..d83c3a4 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -52,25 +52,35 @@
void UidMap::updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
const vector<String16>& packageName) {
+ updateMap(time(nullptr) * 1000000000, uid, versionCode, packageName);
+}
+
+void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
+ const vector<int32_t>& versionCode, const vector<String16>& packageName) {
lock_guard<mutex> lock(mMutex); // Exclusively lock for updates.
mMap.clear();
- for (unsigned long j = 0; j < uid.size(); j++) {
+ for (size_t j = 0; j < uid.size(); j++) {
mMap.insert(make_pair(uid[j],
AppData(string(String8(packageName[j]).string()), versionCode[j])));
}
- if (mOutput.initial_size() == 0) { // Provide the initial states in the mOutput proto
- for (unsigned long j = 0; j < uid.size(); j++) {
- auto t = mOutput.add_initial();
- t->set_app(string(String8(packageName[j]).string()));
- t->set_version(int(versionCode[j]));
- t->set_uid(uid[j]);
- }
+ auto snapshot = mOutput.add_snapshots();
+ snapshot->set_timestamp_nanos(timestamp);
+ for (size_t j = 0; j < uid.size(); j++) {
+ auto t = snapshot->add_package_info();
+ t->set_name(string(String8(packageName[j]).string()));
+ t->set_version(int(versionCode[j]));
+ t->set_uid(uid[j]);
}
}
void UidMap::updateApp(const String16& app_16, const int32_t& uid, const int32_t& versionCode) {
+ updateApp(time(nullptr) * 1000000000, app_16, uid, versionCode);
+}
+
+void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid,
+ const int32_t& versionCode) {
lock_guard<mutex> lock(mMutex);
string app = string(String8(app_16).string());
@@ -82,7 +92,7 @@
auto log = mOutput.add_changes();
log->set_deletion(false);
- // log.timestamp = TODO: choose how timestamps are computed
+ log->set_timestamp_nanos(timestamp);
log->set_app(app);
log->set_uid(uid);
log->set_version(versionCode);
@@ -102,13 +112,20 @@
}
void UidMap::removeApp(const String16& app_16, const int32_t& uid) {
+ removeApp(time(nullptr) * 1000000000, app_16, uid);
+}
+void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
lock_guard<mutex> lock(mMutex);
string app = string(String8(app_16).string());
+ for (auto it : mSubscribers) {
+ it->notifyAppRemoved(app, uid);
+ }
+
auto log = mOutput.add_changes();
log->set_deletion(true);
- // log.timestamp = TODO: choose how timestamps are computed
+ log->set_timestamp_nanos(timestamp);
log->set_app(app);
log->set_uid(uid);
@@ -133,21 +150,67 @@
mSubscribers.erase(producer);
}
-UidMapping UidMap::getAndClearOutput() {
- lock_guard<mutex> lock(mMutex); // Lock for updates
-
- auto ret = UidMapping(mOutput); // Copy that will be returned.
+void UidMap::clearOutput() {
mOutput.Clear();
// Re-initialize the initial state for the outputs. This results in extra data being uploaded
- // but helps ensure we can't re-construct the UID->app name, versionCode mapping in server.
+ // but helps ensure we can re-construct the UID->app name, versionCode mapping in server.
+ auto snapshot = mOutput.add_snapshots();
for (auto it : mMap) {
- auto t = mOutput.add_initial();
- t->set_app(it.second.packageName);
+ auto t = snapshot->add_package_info();
+ t->set_name(it.second.packageName);
t->set_version(it.second.versionCode);
t->set_uid(it.first);
}
+}
+int64_t UidMap::getMinimumTimestampNs() {
+ int64_t m = 0;
+ for (auto it : mLastUpdatePerConfigKey) {
+ if (m == 0) {
+ m = it.second;
+ } else if (it.second < m) {
+ m = it.second;
+ }
+ }
+ return m;
+}
+
+UidMapping UidMap::getOutput(const ConfigKey& key) {
+ return getOutput(time(nullptr) * 1000000000, key);
+}
+
+UidMapping UidMap::getOutput(const int64_t& timestamp, const ConfigKey& key) {
+ lock_guard<mutex> lock(mMutex); // Lock for updates
+
+ auto ret = UidMapping(mOutput); // Copy that will be returned.
+ int64_t prevMin = getMinimumTimestampNs();
+ mLastUpdatePerConfigKey[key] = timestamp;
+ int64_t newMin = getMinimumTimestampNs();
+
+ if (newMin > prevMin) {
+ int64_t cutoff_nanos = newMin;
+ auto snapshots = mOutput.mutable_snapshots();
+ auto it_snapshots = snapshots->cbegin();
+ while (it_snapshots != snapshots->cend()) {
+ if (it_snapshots->timestamp_nanos() < cutoff_nanos) {
+ // it_snapshots now points to the following element.
+ it_snapshots = snapshots->erase(it_snapshots);
+ } else {
+ ++it_snapshots;
+ }
+ }
+ auto deltas = mOutput.mutable_changes();
+ auto it_deltas = deltas->cbegin();
+ while (it_deltas != deltas->cend()) {
+ if (it_deltas->timestamp_nanos() < cutoff_nanos) {
+ // it_deltas now points to the following element.
+ it_deltas = deltas->erase(it_deltas);
+ } else {
+ ++it_deltas;
+ }
+ }
+ }
return ret;
}
@@ -160,6 +223,14 @@
}
}
+void UidMap::OnConfigUpdated(const ConfigKey& key) {
+ mLastUpdatePerConfigKey[key] = -1;
+}
+
+void UidMap::OnConfigRemoved(const ConfigKey& key) {
+ mLastUpdatePerConfigKey.erase(key);
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index d550372..bf120e0 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -17,11 +17,14 @@
#ifndef STATSD_UIDMAP_H
#define STATSD_UIDMAP_H
+#include "config/ConfigKey.h"
+#include "config/ConfigListener.h"
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "packages/PackageInfoListener.h"
#include <binder/IResultReceiver.h>
#include <binder/IShellCallback.h>
+#include <gtest/gtest_prod.h>
#include <log/logprint.h>
#include <stdio.h>
#include <utils/RefBase.h>
@@ -51,17 +54,19 @@
* All three inputs must be the same size, and the jth element in each array refers to the same
* tuple, ie. uid[j] corresponds to packageName[j] with versionCode[j].
*/
+ // TODO: Add safeguards to call clearOutput if there's too much data already stored.
void updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
const vector<String16>& packageName);
+ // TODO: Add safeguards to call clearOutput if there's too much data already stored.
+ void updateApp(const String16& packageName, const int32_t& uid, const int32_t& versionCode);
+ void removeApp(const String16& packageName, const int32_t& uid);
+
// Returns true if the given uid contains the specified app (eg. com.google.android.gms).
bool hasApp(int uid, const string& packageName) const;
int getAppVersion(int uid, const string& packageName) const;
- void updateApp(const String16& packageName, const int32_t& uid, const int32_t& versionCode);
- void removeApp(const String16& packageName, const int32_t& uid);
-
// Helper for debugging contents of this uid map. Can be triggered with:
// adb shell cmd stats print-uid-map
void printUidMap(FILE* out);
@@ -73,13 +78,36 @@
// Remove the listener from the set of metric producers that subscribe to updates.
void removeListener(sp<PackageInfoListener> producer);
- // Grabs the current output contents and then clears it.
- UidMapping getAndClearOutput();
+ // Informs uid map that a config is added/updated. Used for keeping mConfigKeys up to date.
+ void OnConfigUpdated(const ConfigKey& key);
+
+ // Informs uid map that a config is removed. Used for keeping mConfigKeys up to date.
+ void OnConfigRemoved(const ConfigKey& key);
+
+ // Gets the output. If every config key has received the output, then the output is cleared.
+ UidMapping getOutput(const ConfigKey& key);
+
+ // Forces the output to be cleared. We still generate a snapshot based on the current state.
+ // This results in extra data uploaded but helps us reconstruct the uid mapping on the server
+ // in case we lose a previous upload.
+ void clearOutput();
private:
+ void updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
+ const vector<int32_t>& versionCode, const vector<String16>& packageName);
+
+ // TODO: Add safeguards to call clearOutput if there's too much data already stored.
+ void updateApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid,
+ const int32_t& versionCode);
+ void removeApp(const int64_t& timestamp, const String16& packageName, const int32_t& uid);
+
+ UidMapping getOutput(const int64_t& timestamp, const ConfigKey& key);
+
// TODO: Use shared_mutex for improved read-locking if a library can be found in Android.
mutable mutex mMutex;
+ // Maps uid to application data. This must be multimap since there is a feature in Android for
+ // multiple apps to share the same uid.
std::unordered_multimap<int, AppData> mMap;
// We prepare the output proto as apps are updated, so that we can grab the current output.
@@ -87,6 +115,17 @@
// Metric producers that should be notified if there's an upgrade in any app.
set<sp<PackageInfoListener>> mSubscribers;
+
+ // Mapping of config keys we're aware of to the epoch time they last received an update. This
+ // lets us know it's safe to delete events older than the oldest update. The value is nanosec.
+ // Value of -1 denotes this config key has never received an upload.
+ std::unordered_map<ConfigKey, int64_t> mLastUpdatePerConfigKey;
+
+ // Returns the minimum value from mConfigKeys.
+ int64_t getMinimumTimestampNs();
+
+ // Allows unit-test to access private methods.
+ FRIEND_TEST(UidMapTest, TestClearingOutput);
};
} // namespace statsd
diff --git a/cmds/statsd/src/stats_events.proto b/cmds/statsd/src/stats_events.proto
index cd00ba8..51244c6 100644
--- a/cmds/statsd/src/stats_events.proto
+++ b/cmds/statsd/src/stats_events.proto
@@ -35,10 +35,53 @@
* in the format defined here and in stats_log.proto.
*/
message StatsEvent {
- oneof event {
- ScreenStateChanged screen_state_changed = 1;
- ProcessStateChanged process_state_changed = 2;
- WakeLockChanged wakelock_changed = 3;
+ // Pushed events start at 2.
+ oneof pushed {
+ // For StatsLog reasons, 1 is illegal and will not work. Must start at 2.
+ BleScanStateChanged ble_scan_state_changed = 2;
+ BleUnoptimizedScanStateChanged ble_unoptimized_scan_state_changed = 3;
+ BleScanResultReceived ble_scan_result_received = 4;
+ SensorStateChanged sensor_state_changed = 5;
+ GpsScanStateChanged gps_scan_state_changed = 6; // TODO: untested
+ SyncStateChanged sync_state_changed = 7;
+ ScheduledJobStateChanged scheduled_job_state_changed = 8;
+ ScreenBrightnessChanged screen_brightness_changed = 9;
+ // 10-20 are temporarily reserved for wakelocks etc.
+ WakelockStateChanged wakelock_state_changed = 10;
+ UidWakelockStateChanged uid_wakelock_state_changed = 11;
+ LongPartialWakelockStateChanged long_partial_wakelock_state_changed = 12;
+ BatterySaverModeStateChanged battery_saver_mode_state_changed = 21;
+ DeviceIdleModeStateChanged device_idle_mode_state_changed = 22;
+ AudioStateChanged audio_state_changed = 23;
+ MediaCodecActivityChanged media_codec_activity_changed = 24;
+ CameraStateChanged camera_state_changed = 25;
+ FlashlightStateChanged flashlight_state_changed = 26;
+ UidProcessStateChanged uid_process_state_changed = 27;
+ ProcessLifeCycleStateChanged process_life_cycle_state_changed = 28;
+ ScreenStateChanged screen_state_changed = 29;
+ BatteryLevelChanged battery_level_changed = 30;
+ ChargingStateChanged charging_state_changed = 31;
+ PluggedStateChanged plugged_state_changed = 32;
+ DeviceTemperatureReported device_temperature_reported = 33;
+ DeviceOnStatusChanged device_on_status_changed = 34;
+ WakeupAlarmOccurred wakeup_alarm_occurred = 35;
+ KernelWakeupReported kernel_wakeup_reported = 36;
+ WifiLockStateChanged wifi_lock_state_changed = 37;
+ WifiSignalStrengthChanged wifi_signal_strength_changed = 38;
+ WifiScanStateChanged wifi_scan_state_changed = 39;
+ PhoneSignalStrengthChanged phone_signal_strength_changed = 40;
+ SettingChanged setting_changed = 41;
+ ActivityForegroundStateChanged activity_foreground_state_changed = 42;
+ // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
+ }
+
+ // Pulled events will start at field 1000.
+ oneof pulled {
+ WifiBytesTransferred wifi_bytes_transferred = 1000;
+ WifiBytesTransferredByFgBg wifi_bytes_transferred_by_fg_bg = 1001;
+ MobileBytesTransferred mobile_bytes_transferred = 1002;
+ MobileBytesTransferredByFgBg mobile_bytes_transferred_by_fg_bg = 1003;
+ KernelWakelocksReported kernel_wakelocks_reported = 1004;
}
}
@@ -72,7 +115,7 @@
* and those UIDs will be translated in xxx to those strings.
*
* CONVENTIONS:
- * - Events are past tense. e.g. ScreenStateChanged, not ScreenStateChange
+ * - Events are past tense. e.g. ScreenStateChanged, not ScreenStateChange.
* - If there is a UID, it goes first. Think in an object-oriented fashion.
* *****************************************************************************
*/
@@ -98,33 +141,665 @@
}
/**
- * Logs that the state of a process state, as per the activity manager has changed.
+ * Logs that the state of a process state, as per the activity manager, has changed.
*
* Logged from:
* frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
*/
-message ProcessStateChanged {
- // TODO: Use the real (mapped) process states.
+message UidProcessStateChanged {
optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation
// The state.
+ // TODO: Use the real (mapped) process states.
optional int32 state = 2;
}
/**
- * Logs that the state of a wakelock has changed.
+ * Logs that a process started, finished, crashed, or ANRed.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message ProcessLifeCycleStateChanged {
+ // TODO: Use the real (mapped) process states.
+ optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation
+
+ // TODO: What is this?
+ optional string name = 2;
+
+ // The state.
+ // TODO: Use an enum.
+ optional int32 event = 3;
+}
+
+
+
+/**
+ * Logs when the ble scan state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message BleScanStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs when an unoptimized ble scan state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
+message BleUnoptimizedScanStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs reporting of a ble scan finding results.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
+message BleScanResultReceived {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ // Number of ble scan results returned.
+ optional int32 num_of_results = 2;
+}
+
+/**
+ * Logs when a sensor state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message SensorStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ // TODO: Is there a way to get the actual name of the sensor?
+ // The id (int) of the sensor.
+ optional int32 sensor_id = 2;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 3;
+}
+
+
+/**
+ * Logs when GPS state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message GpsScanStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+
+/**
+ * Logs when a sync manager sync state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message SyncStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ // Name of the sync (as named in the app)
+ optional string name = 2;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 3;
+}
+
+/**
+ * Logs when a job scheduler job state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message ScheduledJobStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ // Name of the job (as named in the app)
+ optional string name = 2;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 3;
+
+ // TODO: Consider adding the stopReason (int)
+}
+
+/**
+ * Logs when the audio state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message AudioStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs when the video codec state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message MediaCodecActivityChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs when the flashlight state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message FlashlightStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs when the camera state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message CameraStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs that the state of a wakelock (per app and per wakelock name) has changed.
*
* Logged from:
* TODO
*/
-message WakeLockChanged {
+message WakelockStateChanged {
// TODO: Add attribution instead of uid.
optional int32 uid = 1;
+ // Type of wakelock.
+ enum Type {
+ PARTIAL = 0;
+ FULL = 1;
+ WINDOW = 2;
+ }
+ optional int32 type = 2;
+
+ // The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
+ optional string tag = 3;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 4;
+}
+
+/**
+ * Logs when an app is holding a wakelock, regardless of the wakelock's name.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message UidWakelockStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ // Type of wakelock.
+ enum Type {
+ PARTIAL = 0;
+ FULL = 1;
+ WINDOW = 2;
+ }
+ optional int32 type = 2;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 3;
+}
+
+/**
+ * Logs when a partial wakelock is considered 'long' (over 1 min).
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message LongPartialWakelockStateChanged {
+ // TODO: Add attribution instead of uid?
+ optional int32 uid = 1;
+
// The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
optional string tag = 2;
- // TODO: Use a constant instead of boolean?
- optional bool state = 3;
+ // TODO: I have no idea what this is.
+ optional string history_tag = 3;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 4;
}
+/**
+ * Logs Battery Saver state change.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message BatterySaverModeStateChanged {
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 1;
+}
+
+/**
+ * Logs Doze mode state change.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message DeviceIdleModeStateChanged {
+ // TODO: Use the enum matching BatteryStats.DEVICE_IDLE_MODE_.
+ optional int32 state = 1;
+}
+
+/**
+ * Logs screen brightness level.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message ScreenBrightnessChanged {
+ // Screen brightness level. Should be in [-1, 255] according to PowerManager.java.
+ optional int32 level = 1;
+}
+
+/**
+ * Logs battery level (percent full, from 0 to 100).
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message BatteryLevelChanged {
+ // Battery level. Should be in [0, 100].
+ optional int32 battery_level = 1;
+}
+
+/**
+ * Logs change in charging status of the device.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message ChargingStateChanged {
+ // TODO: Link directly to BatteryManager.java's constants (via a proto).
+ enum State {
+ BATTERY_STATUS_UNKNOWN = 1;
+ BATTERY_STATUS_CHARGING = 2;
+ BATTERY_STATUS_DISCHARGING = 3;
+ BATTERY_STATUS_NOT_CHARGING = 4;
+ BATTERY_STATUS_FULL = 5;
+ }
+ optional State charging_state = 1;
+}
+
+/**
+ * Logs whether the device is plugged in, and what power source it is using.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message PluggedStateChanged {
+ // TODO: Link directly to BatteryManager.java's constants (via a proto).
+ enum State {
+ // Note that NONE is not in BatteryManager.java's constants.
+ BATTERY_PLUGGED_NONE = 0;
+ // Power source is an AC charger.
+ BATTERY_PLUGGED_AC = 1;
+ // Power source is a USB port.
+ BATTERY_PLUGGED_USB = 2;
+ // Power source is wireless.
+ BATTERY_PLUGGED_WIRELESS = 4;
+ }
+ optional State plugged_state = 1;
+}
+
+/**
+ * Logs the temperature of the device, in tenths of a degree Celsius.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message DeviceTemperatureReported {
+ // Temperature in tenths of a degree C.
+ optional int32 temperature = 1;
+}
+
+// TODO: Define this more precisely.
+// TODO: Log the ON state somewhere. It isn't currently logged anywhere.
+/**
+ * Logs when the device turns off or on.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ */
+message DeviceOnStatusChanged {
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 1;
+}
+
+/**
+ * Logs when an app's wakeup alarm fires.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ */
+message WakeupAlarmOccurred {
+ // TODO: Add attribution instead of uid?
+ optional int32 uid = 1;
+}
+
+/**
+ * Logs kernel wakeup reasons and aborts.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message KernelWakeupReported {
+ // Name of the kernel wakeup reason (or abort).
+ optional string wakeup_reason_name = 1;
+
+ // Duration (in microseconds) for the wake-up interrupt to be serviced.
+ optional int64 duration_usec = 2;
+}
+
+/**
+ * Logs wifi locks held by an app.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message WifiLockStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs wifi signal strength changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message WifiSignalStrengthChanged {
+ // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states.
+ enum SignalStrength {
+ SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
+ SIGNAL_STRENGTH_POOR = 1;
+ SIGNAL_STRENGTH_MODERATE = 2;
+ SIGNAL_STRENGTH_GOOD = 3;
+ SIGNAL_STRENGTH_GREAT = 4;
+ }
+ optional SignalStrength signal_strength = 1;
+}
+
+/**
+ * Logs wifi scans performed by an app.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message WifiScanStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs phone signal strength changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message PhoneSignalStrengthChanged {
+ // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states.
+ enum SignalStrength {
+ SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
+ SIGNAL_STRENGTH_POOR = 1;
+ SIGNAL_STRENGTH_MODERATE = 2;
+ SIGNAL_STRENGTH_GOOD = 3;
+ SIGNAL_STRENGTH_GREAT = 4;
+ }
+ optional SignalStrength signal_strength = 1;
+}
+
+/**
+ * Logs that a setting was updated.
+ * Logged from:
+ * frameworks/base/core/java/android/provider/Settings.java
+ * The tag and is_default allow resetting of settings to default values based on the specified
+ * tag. See Settings#putString(ContentResolver, String, String, String, boolean) for more details.
+ */
+message SettingChanged {
+ // The name of the setting.
+ optional string setting = 1;
+
+ // The change being imposed on this setting. May represent a number, eg "3".
+ optional string value = 2;
+
+ // The new value of this setting. For most settings, this is same as value. For some settings,
+ // value is +X or -X where X represents an element in a set. For example, if the previous value
+ // is A,B,C and value is -B, then new_value is A,C and prev_value is A,B,C.
+ // The +/- feature is currently only used for location_providers_allowed.
+ optional string new_value = 3;
+
+ // The previous value of this setting.
+ optional string prev_value = 4;
+
+ // The tag used with the is_default for resetting sets of settings. This is generally null.
+ optional string tag = 5;
+
+ // 1 indicates that this setting with tag should be resettable.
+ optional int32 is_default = 6;
+
+ // The user ID associated. Defined in android/os/UserHandle.java
+ optional int32 user = 7;
+}
+
+
+/*
+ * Logs activity going to foreground or background
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java
+ */
+message ActivityForegroundStateChanged {
+ enum Activity {
+ MOVE_TO_BACKGROUND = 0;
+ MOVE_TO_FOREGROUND = 1;
+ }
+ optional int32 uid = 1;
+ optional string pkg_name = 2;
+ optional string class_name = 3;
+ optional Activity activity = 4;
+}
+
+/**
+ * Pulls bytes transferred via wifi (Sum of foreground and background usage).
+ *
+ * Pulled from:
+ * StatsCompanionService (using BatteryStats to get which interfaces are wifi)
+ */
+message WifiBytesTransferred {
+ optional int32 uid = 1;
+
+ optional int64 rx_bytes = 2;
+
+ optional int64 rx_packets = 3;
+
+ optional int64 tx_bytes = 4;
+
+ optional int64 tx_packets = 5;
+}
+
+/**
+ * Pulls bytes transferred via wifi (separated by foreground and background usage).
+ *
+ * Pulled from:
+ * StatsCompanionService (using BatteryStats to get which interfaces are wifi)
+ */
+message WifiBytesTransferredByFgBg {
+ optional int32 uid = 1;
+
+ // 1 denotes foreground and 0 denotes background. This is called Set in NetworkStats.
+ optional int32 is_foreground = 2;
+
+ optional int64 rx_bytes = 3;
+
+ optional int64 rx_packets = 4;
+
+ optional int64 tx_bytes = 5;
+
+ optional int64 tx_packets = 6;
+}
+
+/**
+ * Pulls bytes transferred via mobile networks (Sum of foreground and background usage).
+ *
+ * Pulled from:
+ * StatsCompanionService (using BatteryStats to get which interfaces are mobile data)
+ */
+message MobileBytesTransferred {
+ optional int32 uid = 1;
+
+ optional int64 rx_bytes = 2;
+
+ optional int64 rx_packets = 3;
+
+ optional int64 tx_bytes = 4;
+
+ optional int64 tx_packets = 5;
+}
+
+/**
+ * Pulls bytes transferred via mobile networks (separated by foreground and background usage).
+ *
+ * Pulled from:
+ * StatsCompanionService (using BatteryStats to get which interfaces are mobile data)
+ */
+message MobileBytesTransferredByFgBg {
+ optional int32 uid = 1;
+
+ // 1 denotes foreground and 0 denotes background. This is called Set in NetworkStats.
+ optional int32 is_foreground = 2;
+
+ optional int64 rx_bytes = 3;
+
+ optional int64 rx_packets = 4;
+
+ optional int64 tx_bytes = 5;
+
+ optional int64 tx_packets = 6;
+}
+
+/**
+ * Pulls the kernel wakelock durations. This atom is adapted from
+ * android/internal/os/KernelWakelockStats.java
+ *
+ * Pulled from:
+ * StatsCompanionService using KernelWakelockReader.
+ */
+message KernelWakelocksReported {
+ optional string name = 1;
+
+ optional int32 count = 2;
+
+ optional int32 version = 3;
+
+ optional int64 time = 4;
+}
diff --git a/cmds/statsd/src/stats_events_copy.proto b/cmds/statsd/src/stats_events_copy.proto
index 5e8ef24..9470372 100644
--- a/cmds/statsd/src/stats_events_copy.proto
+++ b/cmds/statsd/src/stats_events_copy.proto
@@ -40,9 +40,28 @@
*/
message StatsEvent {
oneof event {
- ScreenStateChanged screen_state_changed = 1;
- ProcessStateChanged process_state_changed = 2;
- WakeLockChanged wakelock_changed = 3;
+ // For StatsLog reasons, 1 is illegal and will not work. Must start at 2.
+ BleScanStateChanged ble_scan_state_changed = 2;
+ BleUnoptimizedScanStateChanged ble_unoptimized_scan_state_changed = 3;
+ BleScanResultReceived ble_scan_result_received = 4;
+ SensorStateChanged sensor_state_changed = 5;
+ GpsScanStateChanged gps_scan_state_changed = 6; // TODO: untested
+ SyncStateChanged sync_state_changed = 7;
+ ScheduledJobStateChanged scheduled_job_state_changed = 8;
+ ScreenBrightnessChanged screen_brightness_changed = 9;
+ // 10-20 are temporarily reserved for wakelocks etc.
+ UidWakelockStateChanged uid_wakelock_state_changed = 11;
+ LongPartialWakelockStateChanged long_partial_wakelock_state_changed = 12;
+ BatterySaverModeStateChanged battery_saver_mode_state_changed = 21;
+ DeviceIdleModeStateChanged device_idle_mode_state_changed = 22;
+ AudioStateChanged audio_state_changed = 23;
+ MediaCodecActivityChanged media_codec_activity_changed = 24;
+ CameraStateChanged camera_state_changed = 25;
+ FlashlightStateChanged flashlight_state_changed = 26;
+ UidProcessStateChanged uid_process_state_changed = 27;
+ ProcessLifeCycleStateChanged process_life_cycle_state_changed = 28;
+ ScreenStateChanged screen_state_changed = 29;
+ // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
}
}
@@ -76,7 +95,7 @@
* and those UIDs will be translated in xxx to those strings.
*
* CONVENTIONS:
- * - Events are past tense. e.g. ScreenStateChanged, not ScreenStateChange
+ * - Events are past tense. e.g. ScreenStateChanged, not ScreenStateChange.
* - If there is a UID, it goes first. Think in an object-oriented fashion.
* *****************************************************************************
*/
@@ -102,33 +121,347 @@
}
/**
- * Logs that the state of a process state, as per the activity manager has changed.
+ * Logs that the state of a process state, as per the activity manager, has changed.
*
* Logged from:
* frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
*/
-message ProcessStateChanged {
- // TODO: Use the real (mapped) process states.
+message UidProcessStateChanged {
optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation
// The state.
+ // TODO: Use the real (mapped) process states.
optional int32 state = 2;
}
/**
- * Logs that the state of a wakelock has changed.
+ * Logs that a process started, finished, crashed, or ANRed.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message ProcessLifeCycleStateChanged {
+ // TODO: Use the real (mapped) process states.
+ optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation
+
+ // TODO: What is this?
+ optional string name = 2;
+
+ // The state.
+ // TODO: Use an enum.
+ optional int32 event = 3;
+}
+
+
+
+/**
+ * Logs when the ble scan state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message BleScanStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs when an unoptimized ble scan state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
+message BleUnoptimizedScanStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs reporting of a ble scan finding results.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
+message BleScanResultReceived {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ // Number of ble scan results returned.
+ optional int32 num_of_results = 2;
+}
+
+/**
+ * Logs when a sensor state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message SensorStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ // TODO: Is there a way to get the actual name of the sensor?
+ // The id (int) of the sensor.
+ optional int32 sensor_id = 2;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 3;
+}
+
+
+/**
+ * Logs when GPS state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message GpsScanStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+
+/**
+ * Logs when a sync manager sync state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message SyncStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ // Name of the sync (as named in the app)
+ optional string name = 2;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 3;
+}
+
+/**
+ * Logs when a job scheduler job state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message ScheduledJobStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ // Name of the job (as named in the app)
+ optional string name = 2;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 3;
+
+ // TODO: Consider adding the stopReason (int)
+}
+
+/**
+ * Logs when the audio state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message AudioStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs when the video codec state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message MediaCodecActivityChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs when the flashlight state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message FlashlightStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs when the camera state changes.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message CameraStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
+ * Logs that the state of a wakelock (per app and per wakelock name) has changed.
*
* Logged from:
* TODO
*/
-message WakeLockChanged {
+message WakelockChanged {
// TODO: Add attribution instead of uid.
optional int32 uid = 1;
+ // Type of wakelock.
+ enum Type {
+ PARTIAL = 0;
+ FULL = 1;
+ WINDOW = 2;
+ }
+ optional int32 type = 2;
+
+ // The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
+ optional string tag = 3;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 4;
+}
+
+/**
+ * Logs when an app is holding a wakelock, regardless of the wakelock's name.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message UidWakelockStateChanged {
+ // TODO: Add attribution instead of uid.
+ optional int32 uid = 1;
+
+ // Type of wakelock.
+ enum Type {
+ PARTIAL = 0;
+ FULL = 1;
+ WINDOW = 2;
+ }
+ optional int32 type = 2;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 3;
+}
+
+/**
+ * Logs when a partial wakelock is considered 'long' (over 1 min).
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message LongPartialWakelockStateChanged {
+ // TODO: Add attribution instead of uid?
+ optional int32 uid = 1;
+
// The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
optional string tag = 2;
- // TODO: Use a constant instead of boolean?
- optional bool state = 3;
+ // TODO: I have no idea what this is.
+ optional string history_tag = 3;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 4;
}
+/**
+ * Logs Battery Saver state change.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message BatterySaverModeStateChanged {
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 1;
+}
+
+/**
+ * Logs Doze mode state change.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message DeviceIdleModeStateChanged {
+ // TODO: Use the enum matching BatteryStats.DEVICE_IDLE_MODE_.
+ optional int32 state = 1;
+}
+
+/**
+ * Logs screen brightness level.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message ScreenBrightnessChanged {
+ // Screen brightness level. Should be in [-1, 255] according to PowerManager.java.
+ optional int32 level = 1;
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 29cd94b..1e37ff8 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -69,21 +69,53 @@
repeated DurationBucketInfo bucket_info = 2;
}
+message ValueBucketInfo {
+ optional int64 start_bucket_nanos = 1;
+
+ optional int64 end_bucket_nanos = 2;
+
+ optional int64 value = 3;
+}
+
+message ValueMetricData {
+ repeated KeyValuePair dimension = 1;
+
+ repeated ValueBucketInfo bucket_info = 2;
+}
+
+message GaugeBucketInfo {
+ optional int64 start_bucket_nanos = 1;
+
+ optional int64 end_bucket_nanos = 2;
+
+ optional int64 gauge = 3;
+}
+
+message GaugeMetricData {
+ repeated KeyValuePair dimension = 1;
+
+ repeated GaugeBucketInfo bucket_info = 2;
+}
+
message UidMapping {
- message AppInfo {
- optional string app = 1;
+ message PackageInfoSnapshot {
+ message PackageInfo {
+ optional string name = 1;
- optional int32 version = 2;
+ optional int32 version = 2;
- optional int32 uid = 3;
+ optional int32 uid = 3;
+ }
+ optional int64 timestamp_nanos = 1;
+
+ repeated PackageInfo package_info = 2;
}
-
- repeated AppInfo initial = 1;
+ repeated PackageInfoSnapshot snapshots = 1;
message Change {
optional bool deletion = 1;
- optional int64 timestamp = 2;
+ optional int64 timestamp_nanos = 2;
optional string app = 3;
optional int32 uid = 4;
@@ -108,9 +140,31 @@
message DurationMetricDataWrapper {
repeated DurationMetricData data = 1;
}
+ message ValueMetricDataWrapper {
+ repeated ValueMetricData data = 1;
+ }
+
+ message GaugeMetricDataWrapper {
+ repeated GaugeMetricData data = 1;
+ }
+
oneof data {
EventMetricDataWrapper event_metrics = 4;
CountMetricDataWrapper count_metrics = 5;
DurationMetricDataWrapper duration_metrics = 6;
+ ValueMetricDataWrapper value_metrics = 7;
+ GaugeMetricDataWrapper gauge_metrics = 8;
}
}
+
+message ConfigMetricsReport {
+ message ConfigKey {
+ optional int32 uid = 1;
+ optional string name = 2;
+ }
+ optional ConfigKey config_key = 1;
+
+ repeated StatsLogReport metrics = 2;
+
+ optional UidMapping uid_map = 3;
+}
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 575588b..a428752 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -30,8 +30,18 @@
#define MATCHER_NOT_FOUND -2
#define NANO_SECONDS_IN_A_SECOND (1000 * 1000 * 1000)
+// TODO: Remove the following constants once they are exposed in ProtOutputStream.h
+const uint64_t FIELD_TYPE_SHIFT = 32;
+const uint64_t TYPE_MESSAGE = 11ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_INT64 = 3ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_INT32 = 5ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_FLOAT = 2ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_STRING = 9ULL << FIELD_TYPE_SHIFT;
+
typedef std::string HashableDimensionKey;
+typedef std::map<std::string, HashableDimensionKey> ConditionKey;
+
EventMetricData parse(log_msg msg);
int getTagId(log_msg msg);
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index afb3f2b..87884b33 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -82,6 +82,8 @@
optional bool count_nesting = 3 [default = true];
optional string stop_all = 4;
+
+ repeated KeyMatcher dimension = 5;
}
message Condition {
@@ -114,7 +116,7 @@
optional int32 refractory_period_secs = 4;
- optional int64 trigger_if_gt = 5;
+ optional int64 trigger_if_sum_gt = 5;
}
message EventMetric {
@@ -148,27 +150,43 @@
message DurationMetric {
optional int64 metric_id = 1;
- optional string start = 2;
-
- optional string stop = 3;
-
- optional string stop_all = 4;
+ optional string what = 2;
enum AggregationType {
DURATION_SUM = 1;
DURATION_MAX_SPARSE = 2;
- DURATION_MIN_SPARSE = 3;
}
- optional AggregationType type = 5;
+ optional AggregationType type = 3;
- optional string predicate = 6;
+ optional string predicate = 4;
- repeated KeyMatcher dimension = 7;
+ repeated KeyMatcher dimension = 5;
- optional Bucket bucket = 8;
+ optional Bucket bucket = 6;
- repeated EventConditionLink links = 9;
+ repeated Alert alerts = 7;
+
+ repeated EventConditionLink links = 8;
+
+}
+
+message GaugeMetric {
+ optional int64 metric_id = 1;
+
+ optional string what = 2;
+
+ optional int32 gauge_field = 3;
+
+ optional string condition = 4;
+
+ repeated KeyMatcher dimension = 5;
+
+ optional Bucket bucket = 6;
+
+ repeated Alert alerts = 7;
+
+ repeated EventConditionLink links = 8;
}
message ValueMetric {
@@ -184,24 +202,21 @@
optional Bucket bucket = 6;
+ repeated Alert alerts = 7;
+
+ repeated EventConditionLink links = 8;
+
enum Operation {
- SUM_DIFF = 1;
- MIN_DIFF = 2;
- MAX_DIFF = 3;
- SUM = 4;
- MIN = 5;
- MAX = 6;
- FIRST = 7;
- LAST = 8;
+ SUM = 1;
}
- optional Operation operation = 7;
+ optional Operation operation = 9 [default = SUM];
}
message EventConditionLink {
- optional string condition = 1;
+ optional string condition = 1;
- repeated KeyMatcher key_in_main = 2;
- repeated KeyMatcher key_in_condition = 3;
+ repeated KeyMatcher key_in_main = 2;
+ repeated KeyMatcher key_in_condition = 3;
};
message StatsdConfig {
diff --git a/cmds/statsd/tests/MaxDurationTracker_test.cpp b/cmds/statsd/tests/MaxDurationTracker_test.cpp
new file mode 100644
index 0000000..ae8bf42
--- /dev/null
+++ b/cmds/statsd/tests/MaxDurationTracker_test.cpp
@@ -0,0 +1,115 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "src/condition/ConditionWizard.h"
+#include "src/metrics/duration_helper/MaxDurationTracker.h"
+
+#include <stdio.h>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+using namespace android::os::statsd;
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+class MockConditionWizard : public ConditionWizard {
+public:
+ MOCK_METHOD2(
+ query,
+ ConditionState(const int conditionIndex,
+ const std::map<std::string, HashableDimensionKey>& conditionParameters));
+};
+
+TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ vector<DurationBucketInfo> buckets;
+ ConditionKey key1;
+
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+ MaxDurationTracker tracker(wizard, -1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+ tracker.noteStart("", true, bucketStartTimeNs, key1);
+ tracker.noteStop("", bucketStartTimeNs + 10);
+
+ tracker.noteStart("", true, bucketStartTimeNs + 20, key1);
+ tracker.noteStop("", bucketStartTimeNs + 40);
+
+ tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+ EXPECT_EQ(1u, buckets.size());
+ EXPECT_EQ(20, buckets[0].duration_nanos());
+}
+
+TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ vector<DurationBucketInfo> buckets;
+ ConditionKey key1;
+
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+ MaxDurationTracker tracker(wizard, -1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+ tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
+ tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
+
+ EXPECT_EQ(2u, buckets.size());
+ EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].duration_nanos());
+ EXPECT_EQ((long long)bucketSizeNs, buckets[1].duration_nanos());
+}
+
+TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ ConditionKey key1;
+ key1["APP_BACKGROUND"] = "1:maps|";
+
+ EXPECT_CALL(*wizard, query(_, key1)) // #4
+ .WillOnce(Return(ConditionState::kFalse));
+
+ vector<DurationBucketInfo> buckets;
+
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+ uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ int64_t durationTimeNs = 2 * 1000;
+
+ MaxDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+ tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+
+ tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
+
+ tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+
+ tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+ EXPECT_EQ(1u, buckets.size());
+ EXPECT_EQ(5, buckets[0].duration_nanos());
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index b000e13..e8e4d8b 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -18,6 +18,7 @@
#include "src/matchers/LogMatchingTracker.h"
#include "src/metrics/CountMetricProducer.h"
#include "src/metrics/MetricProducer.h"
+#include "src/metrics/ValueMetricProducer.h"
#include "src/metrics/metrics_manager_util.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
@@ -124,6 +125,39 @@
return config;
}
+StatsdConfig buildDimensionMetricsWithMultiTags() {
+ StatsdConfig config;
+ config.set_config_id(12345L);
+
+ LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("BATTERY_VERY_LOW");
+ SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(2);
+
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("BATTERY_VERY_VERY_LOW");
+ simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(3);
+
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("BATTERY_LOW");
+
+ LogEntryMatcher_Combination* combination = eventMatcher->mutable_combination();
+ combination->set_operation(LogicalOperation::OR);
+ combination->add_matcher("BATTERY_VERY_LOW");
+ combination->add_matcher("BATTERY_VERY_VERY_LOW");
+
+ // Count process state changes, slice by uid, while SCREEN_IS_OFF
+ CountMetric* metric = config.add_count_metric();
+ metric->set_metric_id(3);
+ metric->set_what("BATTERY_LOW");
+ metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ KeyMatcher* keyMatcher = metric->add_dimension();
+ keyMatcher->set_key(1);
+
+ return config;
+}
+
StatsdConfig buildCircleConditions() {
StatsdConfig config;
config.set_config_id(12345L);
@@ -180,6 +214,21 @@
trackerToConditionMap));
}
+TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
+ StatsdConfig config = buildDimensionMetricsWithMultiTags();
+ set<int> allTagIds;
+ vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+ vector<sp<ConditionTracker>> allConditionTrackers;
+ vector<sp<MetricProducer>> allMetricProducers;
+ unordered_map<int, std::vector<int>> conditionToMetricMap;
+ unordered_map<int, std::vector<int>> trackerToMetricMap;
+ unordered_map<int, std::vector<int>> trackerToConditionMap;
+
+ EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
+ allMetricProducers, conditionToMetricMap, trackerToMetricMap,
+ trackerToConditionMap));
+}
+
TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
StatsdConfig config = buildCircleMatchers();
set<int> allTagIds;
diff --git a/cmds/statsd/tests/OringDurationTracker_test.cpp b/cmds/statsd/tests/OringDurationTracker_test.cpp
new file mode 100644
index 0000000..0b79819
--- /dev/null
+++ b/cmds/statsd/tests/OringDurationTracker_test.cpp
@@ -0,0 +1,99 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "src/condition/ConditionWizard.h"
+#include "src/metrics/duration_helper/OringDurationTracker.h"
+
+#include <stdio.h>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+using namespace android::os::statsd;
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+class MockConditionWizard : public ConditionWizard {
+public:
+ MOCK_METHOD2(
+ query,
+ ConditionState(const int conditionIndex,
+ const std::map<std::string, HashableDimensionKey>& conditionParameters));
+};
+
+TEST(OringDurationTrackerTest, TestDurationOverlap) {
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ ConditionKey key1;
+ key1["APP_BACKGROUND"] = "1:maps|";
+
+ vector<DurationBucketInfo> buckets;
+
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+ uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ int64_t durationTimeNs = 2 * 1000;
+
+ OringDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+ tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+ tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
+
+ tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+
+ tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+ EXPECT_EQ(1u, buckets.size());
+ EXPECT_EQ(durationTimeNs, buckets[0].duration_nanos());
+}
+
+TEST(OringDurationTrackerTest, TestDurationConditionChange) {
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ ConditionKey key1;
+ key1["APP_BACKGROUND"] = "1:maps|";
+
+ EXPECT_CALL(*wizard, query(_, key1)) // #4
+ .WillOnce(Return(ConditionState::kFalse));
+
+ vector<DurationBucketInfo> buckets;
+
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+ uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ int64_t durationTimeNs = 2 * 1000;
+
+ OringDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+
+ tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+
+ tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
+
+ tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+
+ tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+ EXPECT_EQ(1u, buckets.size());
+ EXPECT_EQ(5, buckets[0].duration_nanos());
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index f9a90e4..671f6d4 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -13,13 +13,17 @@
// limitations under the License.
#include "packages/UidMap.h"
+#include "config/ConfigKey.h"
#include <gtest/gtest.h>
#include <stdio.h>
using namespace android;
-using namespace android::os::statsd;
+
+namespace android {
+namespace os {
+namespace statsd {
#ifdef __ANDROID__
const string kApp1 = "app1.sharing.1";
@@ -64,6 +68,57 @@
EXPECT_FALSE(m.hasApp(1000, kApp1));
EXPECT_TRUE(m.hasApp(1000, kApp2));
}
+
+TEST(UidMapTest, TestClearingOutput) {
+ UidMap m;
+
+ ConfigKey config1(1, "config1");
+ ConfigKey config2(1, "config2");
+
+ m.OnConfigUpdated(config1);
+
+ vector<int32_t> uids;
+ vector<int32_t> versions;
+ vector<String16> apps;
+ uids.push_back(1000);
+ uids.push_back(1000);
+ apps.push_back(String16(kApp1.c_str()));
+ apps.push_back(String16(kApp2.c_str()));
+ versions.push_back(4);
+ versions.push_back(5);
+ m.updateMap(1, uids, versions, apps);
+
+ UidMapping results = m.getOutput(2, config1);
+ EXPECT_EQ(1, results.snapshots_size());
+
+ // It should be cleared now
+ results = m.getOutput(3, config1);
+ EXPECT_EQ(0, results.snapshots_size());
+
+ // Now add another configuration.
+ m.OnConfigUpdated(config2);
+ m.updateApp(5, String16(kApp1.c_str()), 1000, 40);
+ results = m.getOutput(6, config1);
+ EXPECT_EQ(0, results.snapshots_size());
+ EXPECT_EQ(1, results.changes_size());
+
+ // Now we still haven't been able to delete anything
+ m.updateApp(7, String16(kApp2.c_str()), 1001, 41);
+ results = m.getOutput(8, config1);
+ EXPECT_EQ(0, results.snapshots_size());
+ EXPECT_EQ(2, results.changes_size());
+
+ results = m.getOutput(9, config2);
+ EXPECT_EQ(0, results.snapshots_size());
+ EXPECT_EQ(2, results.changes_size());
+ // At this point both should be cleared.
+ EXPECT_EQ(0, m.mOutput.snapshots_size());
+ EXPECT_EQ(0, m.mOutput.changes_size());
+}
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/config/compiled-classes-phone b/config/compiled-classes-phone
index f32c0d6..47e148d 100644
--- a/config/compiled-classes-phone
+++ b/config/compiled-classes-phone
@@ -709,9 +709,9 @@
android.bluetooth.BluetoothHeadset$2
android.bluetooth.BluetoothHeadset$3
android.bluetooth.BluetoothHealthAppConfiguration
-android.bluetooth.BluetoothInputDevice
-android.bluetooth.BluetoothInputDevice$1
-android.bluetooth.BluetoothInputDevice$2
+android.bluetooth.BluetoothHidHost
+android.bluetooth.BluetoothHidHost$1
+android.bluetooth.BluetoothHidHost$2
android.bluetooth.BluetoothInputStream
android.bluetooth.BluetoothManager
android.bluetooth.BluetoothMap
@@ -754,9 +754,9 @@
android.bluetooth.IBluetoothHealth
android.bluetooth.IBluetoothHealth$Stub
android.bluetooth.IBluetoothHealthCallback
-android.bluetooth.IBluetoothInputDevice
-android.bluetooth.IBluetoothInputDevice$Stub
-android.bluetooth.IBluetoothInputDevice$Stub$Proxy
+android.bluetooth.IBluetoothHidHost
+android.bluetooth.IBluetoothHidHost$Stub
+android.bluetooth.IBluetoothHidHost$Stub$Proxy
android.bluetooth.IBluetoothManager
android.bluetooth.IBluetoothManager$Stub
android.bluetooth.IBluetoothManager$Stub$Proxy
diff --git a/core/java/Android.bp b/core/java/Android.bp
new file mode 100644
index 0000000..42b0f6b
--- /dev/null
+++ b/core/java/Android.bp
@@ -0,0 +1,4 @@
+filegroup {
+ name: "IKeyAttestationApplicationIdProvider.aidl",
+ srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 85f73bb..a8863bf 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,8 +16,6 @@
package android.app;
-import static android.os.Build.VERSION_CODES.O_MR1;
-
import static java.lang.Character.MIN_VALUE;
import android.annotation.CallSuper;
@@ -136,6 +134,7 @@
import java.util.HashMap;
import java.util.List;
+
/**
* An activity is a single, focused thing that the user can do. Almost all
* activities interact with the user, so the Activity class takes care of
@@ -991,17 +990,6 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
- if (getApplicationInfo().targetSdkVersion >= O_MR1 && mActivityInfo.isFixedOrientation()) {
- final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
- final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
- ta.recycle();
-
- if (isTranslucentOrFloating) {
- throw new IllegalStateException(
- "Only fullscreen opaque activities can request orientation");
- }
- }
-
if (mLastNonConfigurationInstances != null) {
mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
}
@@ -5867,10 +5855,11 @@
}
/**
- * Returns complete component name of this activity.
+ * Returns the complete component name of this activity.
*
* @return Returns the complete component name for this activity
*/
+ @Override
public ComponentName getComponentName()
{
return mComponent;
@@ -7303,24 +7292,25 @@
}
/**
- * Request to put this Activity in a mode where the user is locked to the
- * current task.
+ * Request to put this activity in a mode where the user is locked to a restricted set of
+ * applications.
*
- * This will prevent the user from launching other apps, going to settings, or reaching the
- * home screen. This does not include those apps whose {@link android.R.attr#lockTaskMode}
- * values permit launching while locked.
+ * <p>If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns {@code true}
+ * for this component, the current task will be launched directly into LockTask mode. Only apps
+ * whitelisted by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])} can
+ * be launched while LockTask mode is active. The user will not be able to leave this mode
+ * until this activity calls {@link #stopLockTask()}. Calling this method while the device is
+ * already in LockTask mode has no effect.
*
- * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true or
- * lockTaskMode=lockTaskModeAlways for this component then the app will go directly into
- * Lock Task mode. The user will not be able to exit this mode until
- * {@link Activity#stopLockTask()} is called.
+ * <p>Otherwise, the current task will be launched into screen pinning mode. In this case, the
+ * system will prompt the user with a dialog requesting permission to use this mode.
+ * The user can exit at any time through instructions shown on the request dialog. Calling
+ * {@link #stopLockTask()} will also terminate this mode.
*
- * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false
- * then the system will prompt the user with a dialog requesting permission to enter
- * this mode. When entered through this method the user can exit at any time through
- * an action described by the request dialog. Calling stopLockTask will also exit the
- * mode.
+ * <p><strong>Note:</strong> this method can only be called when the activity is foreground.
+ * That is, between {@link #onResume()} and {@link #onPause()}.
*
+ * @see #stopLockTask()
* @see android.R.attr#lockTaskMode
*/
public void startLockTask() {
@@ -7331,25 +7321,24 @@
}
/**
- * Allow the user to switch away from the current task.
+ * Stop the current task from being locked.
*
- * Called to end the mode started by {@link Activity#startLockTask}. This
- * can only be called by activities that have successfully called
- * startLockTask previously.
+ * <p>Called to end the LockTask or screen pinning mode started by {@link #startLockTask()}.
+ * This can only be called by activities that have called {@link #startLockTask()} previously.
*
- * This will allow the user to exit this app and move onto other activities.
- * <p>Note: This method should only be called when the activity is user-facing. That is,
- * between onResume() and onPause().
- * <p>Note: If there are other tasks below this one that are also locked then calling this
- * method will immediately finish this task and resume the previous locked one, remaining in
- * lockTask mode.
+ * <p><strong>Note:</strong> If the device is in LockTask mode that is not initially started
+ * by this activity, then calling this method will not terminate the LockTask mode, but only
+ * finish its own task. The device will remain in LockTask mode, until the activity which
+ * started the LockTask mode calls this method, or until its whitelist authorization is revoked
+ * by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}.
*
+ * @see #startLockTask()
* @see android.R.attr#lockTaskMode
* @see ActivityManager#getLockTaskModeState()
*/
public void stopLockTask() {
try {
- ActivityManager.getService().stopLockTaskMode();
+ ActivityManager.getService().stopLockTaskModeByToken(mToken);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index fc4c8d7..064e978 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -46,6 +46,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.BatteryStats;
+import android.os.Binder;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -500,7 +501,7 @@
public static final int PROCESS_STATE_SERVICE = 11;
/** @hide Process is in the background running a receiver. Note that from the
- * perspective of oom_adj receivers run at a higher foreground level, but for our
+ * perspective of oom_adj, receivers run at a higher foreground level, but for our
* prioritization here that is not necessary and putting them below services means
* many fewer changes in some process states as they receive broadcasts. */
public static final int PROCESS_STATE_RECEIVER = 12;
@@ -524,6 +525,20 @@
/** @hide Process does not exist. */
public static final int PROCESS_STATE_NONEXISTENT = 18;
+ // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added
+ // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must
+ // be updated to correctly map between them.
+ /**
+ * Maps ActivityManager.PROCESS_STATE_ values to ActivityManagerProto.ProcessState enum.
+ *
+ * @param amInt a process state of the form ActivityManager.PROCESS_STATE_
+ * @return the value of the corresponding android.app.ActivityManagerProto's ProcessState enum.
+ * @hide
+ */
+ public static final int processStateAmToProto(int amInt) {
+ return amInt * 100;
+ }
+
/** @hide The lowest process state number */
public static final int MIN_PROCESS_STATE = PROCESS_STATE_PERSISTENT;
@@ -667,20 +682,23 @@
}
/**
- * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
- * specifies the position of the created docked stack at the top half of the screen if
+ * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary}
+ * which specifies the position of the created docked stack at the top half of the screen if
* in portrait mode or at the left half of the screen if in landscape mode.
* @hide
*/
- public static final int DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT = 0;
+ @TestApi
+ public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0;
/**
- * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
+ * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary}
+ * which
* specifies the position of the created docked stack at the bottom half of the screen if
* in portrait mode or at the right half of the screen if in landscape mode.
* @hide
*/
- public static final int DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
+ @TestApi
+ public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
/**
* Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
@@ -833,9 +851,8 @@
/**
* Returns true if this is a low-RAM device. Exactly whether a device is low-RAM
* is ultimately up to the device configuration, but currently it generally means
- * something in the class of a 512MB device with about a 800x480 or less screen.
- * This is mostly intended to be used by apps to determine whether they should turn
- * off certain features that require more RAM.
+ * something with 1GB or less of RAM. This is mostly intended to be used by apps
+ * to determine whether they should turn off certain features that require more RAM.
*/
public boolean isLowRamDevice() {
return isLowRamDeviceStatic();
@@ -1596,6 +1613,9 @@
public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
throws SecurityException {
try {
+ if (maxNum < 0) {
+ throw new IllegalArgumentException("The requested number of tasks should be >= 0");
+ }
return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1882,7 +1902,7 @@
public List<RunningTaskInfo> getRunningTasks(int maxNum)
throws SecurityException {
try {
- return getService().getTasks(maxNum, 0);
+ return getService().getTasks(maxNum);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1908,6 +1928,33 @@
}
/**
+ * Moves the input task to the primary-split-screen stack.
+ * @param taskId Id of task to move.
+ * @param createMode The mode the primary split screen stack should be created in if it doesn't
+ * exist already. See
+ * {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT}
+ * and
+ * {@link android.app.ActivityManager
+ * #SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT}
+ * @param toTop If the task and stack should be moved to the top.
+ * @param animate Whether we should play an animation for the moving the task
+ * @param initialBounds If the primary stack gets created, it will use these bounds for the
+ * docked stack. Pass {@code null} to use default bounds.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ public void setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+ boolean animate, Rect initialBounds) throws SecurityException {
+ try {
+ getService().setTaskWindowingModeSplitScreenPrimary(taskId, createMode, toTop, animate,
+ initialBounds);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Resizes the input stack id to the given bounds.
* @param stackId Id of the stack to resize.
* @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
@@ -3882,21 +3929,36 @@
IBinder service = ServiceManager.checkService(name);
if (service == null) {
pw.println(" (Service not found)");
+ pw.flush();
return;
}
- TransferPipe tp = null;
- try {
- pw.flush();
- tp = new TransferPipe();
- tp.setBufferPrefix(" ");
- service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
- tp.go(fd, 10000);
- } catch (Throwable e) {
- if (tp != null) {
- tp.kill();
+ pw.flush();
+ if (service instanceof Binder) {
+ // If this is a local object, it doesn't make sense to do an async dump with it,
+ // just directly dump.
+ try {
+ service.dump(fd, args);
+ } catch (Throwable e) {
+ pw.println("Failure dumping service:");
+ e.printStackTrace(pw);
+ pw.flush();
}
- pw.println("Failure dumping service:");
- e.printStackTrace(pw);
+ } else {
+ // Otherwise, it is remote, do the dump asynchronously to avoid blocking.
+ TransferPipe tp = null;
+ try {
+ pw.flush();
+ tp = new TransferPipe();
+ tp.setBufferPrefix(" ");
+ service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
+ tp.go(fd, 10000);
+ } catch (Throwable e) {
+ if (tp != null) {
+ tp.kill();
+ }
+ pw.println("Failure dumping service:");
+ e.printStackTrace(pw);
+ }
}
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 9d14f61..a46b3c7 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -64,6 +64,27 @@
public static final int APP_TRANSITION_SNAPSHOT = 4;
/**
+ * The bundle key to extract the assist data.
+ */
+ public static final String ASSIST_KEY_DATA = "data";
+
+ /**
+ * The bundle key to extract the assist structure.
+ */
+ public static final String ASSIST_KEY_STRUCTURE = "structure";
+
+ /**
+ * The bundle key to extract the assist content.
+ */
+ public static final String ASSIST_KEY_CONTENT = "content";
+
+ /**
+ * The bundle key to extract the assist receiver extras.
+ */
+ public static final String ASSIST_KEY_RECEIVER_EXTRAS = "receiverExtras";
+
+
+ /**
* Grant Uri permissions from one app to another. This method only extends
* permission grants if {@code callingUid} has permission to them.
*/
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index b62e4c2..4a21f5c 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -16,7 +16,7 @@
package android.app;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
@@ -203,10 +203,11 @@
"android.activity.taskOverlayCanResume";
/**
- * Where the docked stack should be positioned.
+ * Where the split-screen-primary stack should be positioned.
* @hide
*/
- private static final String KEY_DOCK_CREATE_MODE = "android:activity.dockCreateMode";
+ private static final String KEY_SPLIT_SCREEN_CREATE_MODE =
+ "android:activity.splitScreenCreateMode";
/**
* Determines whether to disallow the outgoing activity from entering picture-in-picture as the
@@ -292,7 +293,7 @@
@WindowConfiguration.ActivityType
private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED;
private int mLaunchTaskId = -1;
- private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ private int mSplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
private boolean mDisallowEnterPictureInPictureWhileLaunching;
private boolean mTaskOverlay;
private boolean mTaskOverlayCanResume;
@@ -884,7 +885,8 @@
mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
- mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
+ mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE,
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean(
KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
if (opts.containsKey(KEY_ANIM_SPECS)) {
@@ -1194,13 +1196,13 @@
}
/** @hide */
- public int getDockCreateMode() {
- return mDockCreateMode;
+ public int getSplitScreenCreateMode() {
+ return mSplitScreenCreateMode;
}
/** @hide */
- public void setDockCreateMode(int dockCreateMode) {
- mDockCreateMode = dockCreateMode;
+ public void setSplitScreenCreateMode(int splitScreenCreateMode) {
+ mSplitScreenCreateMode = splitScreenCreateMode;
}
/** @hide */
@@ -1369,7 +1371,7 @@
b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume);
- b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode);
+ b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode);
b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING,
mDisallowEnterPictureInPictureWhileLaunching);
if (mAnimSpecs != null) {
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 2813e8b..55f9e28 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -33,6 +33,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.proto.ProtoOutputStream;
import libcore.util.ZoneInfoDB;
@@ -48,7 +49,7 @@
* if it is not already running. Registered alarms are retained while the
* device is asleep (and can optionally wake the device up if they go off
* during that time), but will be cleared if it is turned off and rebooted.
- *
+ *
* <p>The Alarm Manager holds a CPU wake lock as long as the alarm receiver's
* onReceive() method is executing. This guarantees that the phone will not sleep
* until you have finished handling the broadcast. Once onReceive() returns, the
@@ -296,7 +297,7 @@
* {@link Intent#EXTRA_ALARM_COUNT Intent.EXTRA_ALARM_COUNT} that indicates
* how many past alarm events have been accumulated into this intent
* broadcast. Recurring alarms that have gone undelivered because the
- * phone was asleep may have a count greater than one when delivered.
+ * phone was asleep may have a count greater than one when delivered.
*
* <div class="note">
* <p>
@@ -396,10 +397,10 @@
* set a recurring alarm for the top of every hour but the phone was asleep
* from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens,
* then the next alarm will be sent at 9:00.
- *
- * <p>If your application wants to allow the delivery times to drift in
+ *
+ * <p>If your application wants to allow the delivery times to drift in
* order to guarantee that at least a certain time interval always elapses
- * between alarms, then the approach to take is to use one-time alarms,
+ * between alarms, then the approach to take is to use one-time alarms,
* scheduling the next one yourself when handling each alarm delivery.
*
* <p class="note">
@@ -1056,7 +1057,7 @@
/**
* Creates a new alarm clock description.
*
- * @param triggerTime time at which the underlying alarm is triggered in wall time
+ * @param triggerTime time at which the underlying alarm is triggered in wall time
* milliseconds since the epoch
* @param showIntent an intent that can be used to show or edit details of
* the alarm clock.
@@ -1089,7 +1090,7 @@
* Returns an intent that can be used to show or edit details of the alarm clock in
* the application that scheduled it.
*
- * <p class="note">Beware that any application can retrieve and send this intent,
+ * <p class="note">Beware that any application can retrieve and send this intent,
* potentially with additional fields filled in. See
* {@link PendingIntent#send(android.content.Context, int, android.content.Intent)
* PendingIntent.send()} and {@link android.content.Intent#fillIn Intent.fillIn()}
@@ -1121,5 +1122,13 @@
return new AlarmClockInfo[size];
}
};
+
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(AlarmClockInfoProto.TRIGGER_TIME_MS, mTriggerTime);
+ mShowIntent.writeToProto(proto, AlarmClockInfoProto.SHOW_INTENT);
+ proto.end(token);
+ }
}
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4bd85ae..b6fb120 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -254,8 +254,10 @@
public static final int OP_ANSWER_PHONE_CALLS = 69;
/** @hide Run jobs when in background */
public static final int OP_RUN_ANY_IN_BACKGROUND = 70;
+ /** @hide Change Wi-Fi connectivity state */
+ public static final int OP_CHANGE_WIFI_STATE = 71;
/** @hide */
- public static final int _NUM_OP = 71;
+ public static final int _NUM_OP = 72;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -496,6 +498,7 @@
OP_INSTANT_APP_START_FOREGROUND,
OP_ANSWER_PHONE_CALLS,
OP_RUN_ANY_IN_BACKGROUND,
+ OP_CHANGE_WIFI_STATE,
};
/**
@@ -574,6 +577,7 @@
OPSTR_INSTANT_APP_START_FOREGROUND,
OPSTR_ANSWER_PHONE_CALLS,
null, // OP_RUN_ANY_IN_BACKGROUND
+ null, // OP_CHANGE_WIFI_STATE
};
/**
@@ -652,6 +656,7 @@
"INSTANT_APP_START_FOREGROUND",
"ANSWER_PHONE_CALLS",
"RUN_ANY_IN_BACKGROUND",
+ "CHANGE_WIFI_STATE",
};
/**
@@ -730,6 +735,7 @@
Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
Manifest.permission.ANSWER_PHONE_CALLS,
null, // no permission for OP_RUN_ANY_IN_BACKGROUND
+ Manifest.permission.CHANGE_WIFI_STATE,
};
/**
@@ -809,6 +815,7 @@
null, // INSTANT_APP_START_FOREGROUND
null, // ANSWER_PHONE_CALLS
null, // OP_RUN_ANY_IN_BACKGROUND
+ null, // OP_CHANGE_WIFI_STATE
};
/**
@@ -887,6 +894,7 @@
false, // INSTANT_APP_START_FOREGROUND
false, // ANSWER_PHONE_CALLS
false, // OP_RUN_ANY_IN_BACKGROUND
+ false, // OP_CHANGE_WIFI_STATE
};
/**
@@ -964,6 +972,7 @@
AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND
AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
AppOpsManager.MODE_ALLOWED, // OP_RUN_ANY_IN_BACKGROUND
+ AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE
};
/**
@@ -1045,6 +1054,7 @@
false,
false, // ANSWER_PHONE_CALLS
false, // OP_RUN_ANY_IN_BACKGROUND
+ false, // OP_CHANGE_WIFI_STATE
};
/**
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 117854a..388459f 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -63,6 +63,7 @@
import android.os.PersistableBundle;
import android.os.StrictMode;
import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IAssistDataReceiver;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.IResultReceiver;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -82,7 +83,7 @@
// below block of transactions.
// Since these transactions are also called from native code, these must be kept in sync with
- // the ones in frameworks/native/include/binder/IActivityManager.h
+ // the ones in frameworks/native/libs/binder/include/binder/IActivityManager.h
// =============== Beginning of transactions used on native side as well ======================
ParcelFileDescriptor openContentUri(in String uriString);
// =============== End of transactions used on native side as well ============================
@@ -115,7 +116,9 @@
in PersistableBundle persistentState, in CharSequence description);
String getCallingPackage(in IBinder token);
ComponentName getCallingActivity(in IBinder token);
- List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, int flags);
+ List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
+ List<ActivityManager.RunningTaskInfo> getFilteredTasks(int maxNum, int ignoreActivityType,
+ int ignoreWindowingMode);
void moveTaskToFront(int task, int flags, in Bundle options);
void moveTaskBackwards(int task);
int getTaskForActivity(in IBinder token, in boolean onlyRoot);
@@ -229,7 +232,7 @@
boolean requireFull, in String name, in String callerPackage);
void addPackageDependency(in String packageName);
void killApplication(in String pkg, int appId, int userId, in String reason);
- void closeSystemDialogs(in String reason);
+ oneway void closeSystemDialogs(in String reason);
Debug.MemoryInfo[] getProcessMemoryInfo(in int[] pids);
void killApplicationProcess(in String processName, int uid);
int startActivityIntentSender(in IApplicationThread caller,
@@ -410,7 +413,7 @@
String getTagForIntentSender(in IIntentSender sender, in String prefix);
boolean startUserInBackground(int userid);
void startLockTaskModeByToken(in IBinder token);
- void stopLockTaskMode();
+ void stopLockTaskModeByToken(in IBinder token);
boolean isInLockTaskMode();
void setTaskDescription(in IBinder token, in ActivityManager.TaskDescription values);
int startVoiceActivity(in String callingPackage, int callingPid, int callingUid,
@@ -419,6 +422,9 @@
in Bundle options, int userId);
int startAssistantActivity(in String callingPackage, int callingPid, int callingUid,
in Intent intent, in String resolvedType, in Bundle options, int userId);
+ int startRecentsActivity(in IAssistDataReceiver assistDataReceiver, in Bundle options,
+ int userId);
+ int startActivityFromRecents(int taskId, in Bundle options);
Bundle getActivityOptions(in IBinder token);
List<IBinder> getAppTasks(in String callingPackage);
void startSystemLockTaskMode(int taskId);
@@ -426,7 +432,6 @@
void finishVoiceTask(in IVoiceInteractionSession session);
boolean isTopOfTask(in IBinder token);
void notifyLaunchTaskBehindComplete(in IBinder token);
- int startActivityFromRecents(int taskId, in Bundle options);
void notifyEnterAnimationComplete(in IBinder token);
int startActivityAsCaller(in IApplicationThread caller, in String callingPackage,
in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
@@ -445,13 +450,12 @@
int checkPermissionWithToken(in String permission, int pid, int uid,
in IBinder callerToken);
void registerTaskStackListener(in ITaskStackListener listener);
+ void unregisterTaskStackListener(in ITaskStackListener listener);
-
- // Start of M transactions
void notifyCleartextNetwork(int uid, in byte[] firstPacket);
int createStackOnDisplay(int displayId);
void setTaskResizeable(int taskId, int resizeableMode);
- boolean requestAssistContextExtras(int requestType, in IResultReceiver receiver,
+ boolean requestAssistContextExtras(int requestType, in IAssistDataReceiver receiver,
in Bundle receiverExtras, in IBinder activityToken,
boolean focused, boolean newSessionId);
void resizeTask(int taskId, in Rect bounds, int resizeMode);
@@ -497,8 +501,8 @@
void exitFreeformMode(in IBinder token);
void reportSizeConfigurations(in IBinder token, in int[] horizontalSizeConfiguration,
in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations);
- boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
- in Rect initialBounds);
+ boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+ boolean animate, in Rect initialBounds);
/**
* Dismisses split-screen multi-window mode.
* {@param toTop} If true the current primary split-screen stack will be placed or left on top.
@@ -613,9 +617,8 @@
* @return Returns true if the configuration was updated.
*/
boolean updateDisplayOverrideConfiguration(in Configuration values, int displayId);
- void unregisterTaskStackListener(ITaskStackListener listener);
void moveStackToDisplay(int stackId, int displayId);
- boolean requestAutofillData(in IResultReceiver receiver, in Bundle receiverExtras,
+ boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
in IBinder activityToken, int flags);
void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
int restartUserInBackground(int userId);
@@ -652,7 +655,10 @@
/**
* Add a bare uid to the background restrictions whitelist. Only the system uid may call this.
*/
- void backgroundWhitelistUid(int uid);
+ void backgroundWhitelistUid(int uid);
+
+ // Start of P transactions
+ void updateLockTaskFeatures(int userId, int flags);
// WARNING: when these transactions are updated, check if they are any callers on the native
// side. If so, make sure they are using the correct transaction ids and arguments.
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index d4752a7..9e926bd 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -138,6 +138,7 @@
void setNotificationPolicy(String pkg, in NotificationManager.Policy policy);
boolean isNotificationPolicyAccessGrantedForPackage(String pkg);
void setNotificationPolicyAccessGranted(String pkg, boolean granted);
+ void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted);
AutomaticZenRule getAutomaticZenRule(String id);
List<ZenModeConfig.ZenRule> getZenRules();
String addAutomaticZenRule(in AutomaticZenRule automaticZenRule);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index fee7d6c..8226e0f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -858,7 +858,7 @@
*
* @hide
*/
- static public IBinder whitelistToken;
+ private IBinder mWhitelistToken;
/**
* Must be set by a process to start associating tokens with Notification objects
@@ -1876,12 +1876,12 @@
{
int version = parcel.readInt();
- whitelistToken = parcel.readStrongBinder();
- if (whitelistToken == null) {
- whitelistToken = processWhitelistToken;
+ mWhitelistToken = parcel.readStrongBinder();
+ if (mWhitelistToken == null) {
+ mWhitelistToken = processWhitelistToken;
}
// Propagate this token to all pending intents that are unmarshalled from the parcel.
- parcel.setClassCookie(PendingIntent.class, whitelistToken);
+ parcel.setClassCookie(PendingIntent.class, mWhitelistToken);
when = parcel.readLong();
creationTime = parcel.readLong();
@@ -1989,7 +1989,7 @@
* @hide
*/
public void cloneInto(Notification that, boolean heavy) {
- that.whitelistToken = this.whitelistToken;
+ that.mWhitelistToken = this.mWhitelistToken;
that.when = this.when;
that.creationTime = this.creationTime;
that.mSmallIcon = this.mSmallIcon;
@@ -2219,7 +2219,7 @@
private void writeToParcelImpl(Parcel parcel, int flags) {
parcel.writeInt(1);
- parcel.writeStrongBinder(whitelistToken);
+ parcel.writeStrongBinder(mWhitelistToken);
parcel.writeLong(when);
parcel.writeLong(creationTime);
if (mSmallIcon == null && icon != 0) {
@@ -4981,6 +4981,8 @@
mN.flags |= FLAG_SHOW_LIGHTS;
}
+ mN.allPendingIntents = null;
+
return mN;
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index a25c226..8b76cc7 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -33,6 +33,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.AndroidException;
+import android.util.proto.ProtoOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1081,7 +1082,16 @@
sb.append('}');
return sb.toString();
}
-
+
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ if (mTarget != null) {
+ proto.write(PendingIntentProto.TARGET, mTarget.asBinder().toString());
+ }
+ proto.end(token);
+ }
+
public int describeContents() {
return 0;
}
@@ -1119,8 +1129,13 @@
*/
public static void writePendingIntentOrNullToParcel(@Nullable PendingIntent sender,
@NonNull Parcel out) {
- out.writeStrongBinder(sender != null ? sender.mTarget.asBinder()
- : null);
+ out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null);
+ if (sender != null) {
+ OnMarshaledListener listener = sOnMarshaledListener.get();
+ if (listener != null) {
+ listener.onMarshaled(sender, out, 0 /* flags */);
+ }
+ }
}
/**
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 251863c..de27b4f 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -102,7 +102,7 @@
public static final int ACTIVITY_TYPE_STANDARD = 1;
/** Home/Launcher activity type. */
public static final int ACTIVITY_TYPE_HOME = 2;
- /** Recents/Overview activity type. */
+ /** Recents/Overview activity type. There is only one activity with this type in the system. */
public static final int ACTIVITY_TYPE_RECENTS = 3;
/** Assistant activity type. */
public static final int ACTIVITY_TYPE_ASSISTANT = 4;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ab8edee..772c6d6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1542,6 +1542,92 @@
public @interface ProvisioningPreCondition {}
/**
+ * Disable all configurable SystemUI features during LockTask mode. This includes,
+ * <ul>
+ * <li>system info area in the status bar (connectivity icons, clock, etc.)
+ * <li>notifications (including alerts, icons, and the notification shade)
+ * <li>Home button
+ * <li>Recents button and UI
+ * <li>global actions menu (i.e. power button menu)
+ * <li>keyguard
+ * </ul>
+ *
+ * This is the default configuration for LockTask.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_NONE = 0;
+
+ /**
+ * Enable the system info area in the status bar during LockTask mode. The system info area
+ * usually occupies the right side of the status bar (although this can differ across OEMs). It
+ * includes all system information indicators, such as date and time, connectivity, battery,
+ * vibration mode, etc.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1;
+
+ /**
+ * Enable notifications during LockTask mode. This includes notification icons on the status
+ * bar, heads-up notifications, and the expandable notification shade. Note that the Quick
+ * Settings panel will still be disabled.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 1 << 1;
+
+ /**
+ * Enable the Home button during LockTask mode. Note that if a custom launcher is used, it has
+ * to be registered as the default launcher with
+ * {@link #addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}, and its
+ * package needs to be whitelisted for LockTask with
+ * {@link #setLockTaskPackages(ComponentName, String[])}.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_HOME = 1 << 2;
+
+ /**
+ * Enable the Recents button and the Recents screen during LockTask mode.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_RECENTS = 1 << 3;
+
+ /**
+ * Enable the global actions dialog during LockTask mode. This is the dialog that shows up when
+ * the user long-presses the power button, for example. Note that the user may not be able to
+ * power off the device if this flag is not set.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 1 << 4;
+
+ /**
+ * Enable the keyguard during LockTask mode. Note that if the keyguard is already disabled with
+ * {@link #setKeyguardDisabled(ComponentName, boolean)}, setting this flag will have no effect.
+ * If this flag is not set, the keyguard will not be shown even if the user has a lock screen
+ * credential.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_KEYGUARD = 1 << 5;
+
+ /**
+ * Flags supplied to {@link #setLockTaskFeatures(ComponentName, int)}.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {LOCK_TASK_FEATURE_NONE, LOCK_TASK_FEATURE_SYSTEM_INFO,
+ LOCK_TASK_FEATURE_NOTIFICATIONS, LOCK_TASK_FEATURE_HOME,
+ LOCK_TASK_FEATURE_RECENTS, LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+ LOCK_TASK_FEATURE_KEYGUARD})
+ public @interface LockTaskFeature {}
+
+ /**
* Service action: Action for a service that device owner and profile owner can optionally
* own. If a device owner or a profile owner has such a service, the system tries to keep
* a bound connection to it, in order to keep their process always running.
@@ -6484,6 +6570,61 @@
}
/**
+ * Sets which system features to enable for LockTask mode.
+ * <p>
+ * Feature flags set through this method will only take effect for the duration when the device
+ * is in LockTask mode. If this method is not called, none of the features listed here will be
+ * enabled.
+ * <p>
+ * This function can only be called by the device owner or by a profile owner of a user/profile
+ * that is affiliated with the device owner user. See {@link #setAffiliationIds}. Any features
+ * set via this method will be cleared if the user becomes unaffiliated.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param flags Bitfield of feature flags:
+ * {@link #LOCK_TASK_FEATURE_NONE} (default),
+ * {@link #LOCK_TASK_FEATURE_SYSTEM_INFO},
+ * {@link #LOCK_TASK_FEATURE_NOTIFICATIONS},
+ * {@link #LOCK_TASK_FEATURE_HOME},
+ * {@link #LOCK_TASK_FEATURE_RECENTS},
+ * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS},
+ * {@link #LOCK_TASK_FEATURE_KEYGUARD}
+ * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
+ * an affiliated user or profile.
+ */
+ public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) {
+ throwIfParentInstance("setLockTaskFeatures");
+ if (mService != null) {
+ try {
+ mService.setLockTaskFeatures(admin, flags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Gets which system features are enabled for LockTask mode.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list.
+ * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
+ * an affiliated user or profile.
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public @LockTaskFeature int getLockTaskFeatures(@NonNull ComponentName admin) {
+ throwIfParentInstance("getLockTaskFeatures");
+ if (mService != null) {
+ try {
+ return mService.getLockTaskFeatures(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return 0;
+ }
+
+ /**
* Called by device owners to update {@link android.provider.Settings.Global} settings.
* Validation that the value of the setting is in the correct form for the setting type should
* be performed by the caller.
@@ -6901,6 +7042,12 @@
* Called by device owner to disable the status bar. Disabling the status bar blocks
* notifications, quick settings and other screen overlays that allow escaping from a single use
* device.
+ * <p>
+ * <strong>Note:</strong> This method has no effect for LockTask mode. The behavior of the
+ * status bar in LockTask mode can be configured with
+ * {@link #setLockTaskFeatures(ComponentName, int)}. Calls to this method when the device is in
+ * LockTask mode will be registered, but will only take effect when the device leaves LockTask
+ * mode.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param disabled {@code true} disables the status bar, {@code false} reenables it.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index e77c186..be0b920 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -226,6 +226,9 @@
String[] getLockTaskPackages(in ComponentName who);
boolean isLockTaskPermitted(in String pkg);
+ void setLockTaskFeatures(in ComponentName who, int flags);
+ int getLockTaskFeatures(in ComponentName who);
+
void setGlobalSetting(in ComponentName who, in String setting, in String value);
void setSecureSetting(in ComponentName who, in String setting, in String value);
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 1434c9b..b640bd5 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -18,6 +18,7 @@
import static android.util.TimeUtils.formatDuration;
+import android.annotation.BytesLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -71,6 +72,9 @@
/** This job requires metered connectivity such as most cellular data networks. */
public static final int NETWORK_TYPE_METERED = 4;
+ /** Sentinel value indicating that bytes are unknown. */
+ public static final int NETWORK_BYTES_UNKNOWN = -1;
+
/**
* Amount of backoff a job has initially by default, in milliseconds.
*/
@@ -250,6 +254,7 @@
private final boolean hasEarlyConstraint;
private final boolean hasLateConstraint;
private final int networkType;
+ private final long networkBytes;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
private final boolean isPeriodic;
@@ -387,6 +392,18 @@
}
/**
+ * Return the estimated size of network traffic that will be performed by
+ * this job, in bytes.
+ *
+ * @return Estimated size of network traffic, or
+ * {@link #NETWORK_BYTES_UNKNOWN} when unknown.
+ * @see Builder#setEstimatedNetworkBytes(long)
+ */
+ public @BytesLong long getEstimatedNetworkBytes() {
+ return networkBytes;
+ }
+
+ /**
* Set for a job that does not recur periodically, to specify a delay after which the job
* will be eligible for execution. This value is not set if the job recurs periodically.
*/
@@ -524,6 +541,9 @@
if (networkType != j.networkType) {
return false;
}
+ if (networkBytes != j.networkBytes) {
+ return false;
+ }
if (minLatencyMillis != j.minLatencyMillis) {
return false;
}
@@ -582,6 +602,7 @@
hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint);
hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint);
hashCode = 31 * hashCode + networkType;
+ hashCode = 31 * hashCode + Long.hashCode(networkBytes);
hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis);
hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis);
hashCode = 31 * hashCode + Boolean.hashCode(isPeriodic);
@@ -612,6 +633,7 @@
triggerContentUpdateDelay = in.readLong();
triggerContentMaxDelay = in.readLong();
networkType = in.readInt();
+ networkBytes = in.readLong();
minLatencyMillis = in.readLong();
maxExecutionDelayMillis = in.readLong();
isPeriodic = in.readInt() == 1;
@@ -640,6 +662,7 @@
triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
triggerContentMaxDelay = b.mTriggerContentMaxDelay;
networkType = b.mNetworkType;
+ networkBytes = b.mNetworkBytes;
minLatencyMillis = b.mMinLatencyMillis;
maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
isPeriodic = b.mIsPeriodic;
@@ -677,6 +700,7 @@
out.writeLong(triggerContentUpdateDelay);
out.writeLong(triggerContentMaxDelay);
out.writeInt(networkType);
+ out.writeLong(networkBytes);
out.writeLong(minLatencyMillis);
out.writeLong(maxExecutionDelayMillis);
out.writeInt(isPeriodic ? 1 : 0);
@@ -810,6 +834,7 @@
// Requirements.
private int mConstraintFlags;
private int mNetworkType;
+ private long mNetworkBytes = NETWORK_BYTES_UNKNOWN;
private ArrayList<TriggerContentUri> mTriggerContentUris;
private long mTriggerContentUpdateDelay = -1;
private long mTriggerContentMaxDelay = -1;
@@ -909,12 +934,21 @@
}
/**
- * Set some description of the kind of network type your job needs to have.
- * Not calling this function means the network is not necessary, as the default is
- * {@link #NETWORK_TYPE_NONE}.
- * Bear in mind that calling this function defines network as a strict requirement for your
- * job. If the network requested is not available your job will never run. See
- * {@link #setOverrideDeadline(long)} to change this behaviour.
+ * Set some description of the kind of network type your job needs to
+ * have. Not calling this function means the network is not necessary,
+ * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that
+ * calling this function defines network as a strict requirement for
+ * your job. If the network requested is not available your job will
+ * never run. See {@link #setOverrideDeadline(long)} to change this
+ * behaviour.
+ * <p class="note">
+ * Note: When your job executes in
+ * {@link JobService#onStartJob(JobParameters)}, be sure to use the
+ * specific network returned by {@link JobParameters#getNetwork()},
+ * otherwise you'll use the default network which may not meet this
+ * constraint.
+ *
+ * @see JobParameters#getNetwork()
*/
public Builder setRequiredNetworkType(@NetworkType int networkType) {
mNetworkType = networkType;
@@ -922,6 +956,43 @@
}
/**
+ * Set the estimated size of network traffic that will be performed by
+ * this job, in bytes.
+ * <p>
+ * Apps are encouraged to provide values that are as accurate as
+ * possible, but when the exact size isn't available, an
+ * order-of-magnitude estimate can be provided instead. Here are some
+ * specific examples:
+ * <ul>
+ * <li>A job that is backing up a photo knows the exact size of that
+ * photo, so it should provide that size as the estimate.
+ * <li>A job that refreshes top news stories wouldn't know an exact
+ * size, but if the size is expected to be consistently around 100KB, it
+ * can provide that order-of-magnitude value as the estimate.
+ * <li>A job that synchronizes email could end up using an extreme range
+ * of data, from under 1KB when nothing has changed, to dozens of MB
+ * when there are new emails with attachments. Jobs that cannot provide
+ * reasonable estimates should leave this estimated value undefined.
+ * </ul>
+ * Note that the system may choose to delay jobs with large network
+ * usage estimates when the device has a poor network connection, in
+ * order to save battery.
+ *
+ * @param networkBytes The estimated size of network traffic that will
+ * be performed by this job, in bytes. This value only
+ * reflects the traffic that will be performed by the base
+ * job; if you're using {@link JobWorkItem} then you also
+ * need to define the network traffic used by each work item
+ * when constructing them.
+ * @see JobInfo#getEstimatedNetworkBytes()
+ * @see JobWorkItem#JobWorkItem(android.content.Intent, long)
+ */
+ public Builder setEstimatedNetworkBytes(@BytesLong long networkBytes) {
+ mNetworkBytes = networkBytes;
+ return this;
+ }
+
+ /**
* Specify that to run this job, the device must be charging (or be a
* non-battery-powered device connected to permanent power, such as Android TV
* devices). This defaults to {@code false}.
@@ -1147,6 +1218,11 @@
throw new IllegalArgumentException("You're trying to build a job with no " +
"constraints, this is not allowed.");
}
+ // Check that network estimates require network type
+ if (mNetworkBytes > 0 && mNetworkType == NETWORK_TYPE_NONE) {
+ throw new IllegalArgumentException(
+ "Can't provide estimated network usage without requiring a network");
+ }
// Check that a deadline was not set on a periodic job.
if (mIsPeriodic) {
if (mMaxExecutionDelayMillis != 0L) {
diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java
index a6f6be2..5053dc6 100644
--- a/core/java/android/app/job/JobParameters.java
+++ b/core/java/android/app/job/JobParameters.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.app.job.IJobCallback;
import android.content.ClipData;
+import android.net.Network;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
@@ -66,6 +67,7 @@
private final boolean overrideDeadlineExpired;
private final Uri[] mTriggeredContentUris;
private final String[] mTriggeredContentAuthorities;
+ private final Network network;
private int stopReason; // Default value of stopReason is REASON_CANCELED
@@ -73,7 +75,7 @@
public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
Bundle transientExtras, ClipData clipData, int clipGrantFlags,
boolean overrideDeadlineExpired, Uri[] triggeredContentUris,
- String[] triggeredContentAuthorities) {
+ String[] triggeredContentAuthorities, Network network) {
this.jobId = jobId;
this.extras = extras;
this.transientExtras = transientExtras;
@@ -83,6 +85,7 @@
this.overrideDeadlineExpired = overrideDeadlineExpired;
this.mTriggeredContentUris = triggeredContentUris;
this.mTriggeredContentAuthorities = triggeredContentAuthorities;
+ this.network = network;
}
/**
@@ -171,6 +174,28 @@
}
/**
+ * Return the network that should be used to perform any network requests
+ * for this job.
+ * <p>
+ * Devices may have multiple active network connections simultaneously, or
+ * they may not have a default network route at all. To correctly handle all
+ * situations like this, your job should always use the network returned by
+ * this method instead of implicitly using the default network route.
+ * <p>
+ * Note that the system may relax the constraints you originally requested,
+ * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over
+ * a metered network when there is a surplus of metered data available.
+ *
+ * @return the network that should be used to perform any network requests
+ * for this job, or {@code null} if this job didn't set any required
+ * network type.
+ * @see JobInfo.Builder#setRequiredNetworkType(int)
+ */
+ public @Nullable Network getNetwork() {
+ return network;
+ }
+
+ /**
* Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their
* currently running job. Calling this method when there is no more work available and all
* previously dequeued work has been completed will result in the system taking care of
@@ -257,6 +282,11 @@
overrideDeadlineExpired = in.readInt() == 1;
mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
mTriggeredContentAuthorities = in.createStringArray();
+ if (in.readInt() != 0) {
+ network = Network.CREATOR.createFromParcel(in);
+ } else {
+ network = null;
+ }
stopReason = in.readInt();
}
@@ -286,6 +316,12 @@
dest.writeInt(overrideDeadlineExpired ? 1 : 0);
dest.writeTypedArray(mTriggeredContentUris, flags);
dest.writeStringArray(mTriggeredContentAuthorities);
+ if (network != null) {
+ dest.writeInt(1);
+ network.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
dest.writeInt(stopReason);
}
diff --git a/core/java/android/app/job/JobWorkItem.java b/core/java/android/app/job/JobWorkItem.java
index 0eb0450..1c46e8e 100644
--- a/core/java/android/app/job/JobWorkItem.java
+++ b/core/java/android/app/job/JobWorkItem.java
@@ -16,6 +16,7 @@
package android.app.job;
+import android.annotation.BytesLong;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,6 +28,7 @@
*/
final public class JobWorkItem implements Parcelable {
final Intent mIntent;
+ final long mNetworkBytes;
int mDeliveryCount;
int mWorkId;
Object mGrants;
@@ -39,6 +41,22 @@
*/
public JobWorkItem(Intent intent) {
mIntent = intent;
+ mNetworkBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+ }
+
+ /**
+ * Create a new piece of work, which can be submitted to
+ * {@link JobScheduler#enqueue JobScheduler.enqueue}.
+ *
+ * @param intent The general Intent describing this work.
+ * @param networkBytes The estimated size of network traffic that will be
+ * performed by this job work item, in bytes. See
+ * {@link JobInfo.Builder#setEstimatedNetworkBytes(long)} for
+ * details about how to estimate.
+ */
+ public JobWorkItem(Intent intent, @BytesLong long networkBytes) {
+ mIntent = intent;
+ mNetworkBytes = networkBytes;
}
/**
@@ -49,6 +67,17 @@
}
/**
+ * Return the estimated size of network traffic that will be performed by
+ * this job work item, in bytes.
+ *
+ * @return estimated size, or {@link JobInfo#NETWORK_BYTES_UNKNOWN} when
+ * unknown.
+ */
+ public @BytesLong long getEstimatedNetworkBytes() {
+ return mNetworkBytes;
+ }
+
+ /**
* Return the count of the number of times this work item has been delivered
* to the job. The value will be > 1 if it has been redelivered because the job
* was stopped or crashed while it had previously been delivered but before the
@@ -99,6 +128,10 @@
sb.append(mWorkId);
sb.append(" intent=");
sb.append(mIntent);
+ if (mNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ sb.append(" networkBytes=");
+ sb.append(mNetworkBytes);
+ }
if (mDeliveryCount != 0) {
sb.append(" dcount=");
sb.append(mDeliveryCount);
@@ -118,6 +151,7 @@
} else {
out.writeInt(0);
}
+ out.writeLong(mNetworkBytes);
out.writeInt(mDeliveryCount);
out.writeInt(mWorkId);
}
@@ -139,6 +173,7 @@
} else {
mIntent = null;
}
+ mNetworkBytes = in.readLong();
mDeliveryCount = in.readInt();
mWorkId = in.readInt();
}
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index 7f9f74b..f6b6b86 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -154,25 +154,6 @@
return Arrays.asList(mHints);
}
- /**
- * @hide
- */
- public SliceItem getPrimaryIcon() {
- for (SliceItem item : getItems()) {
- if (item.getType() == SliceItem.TYPE_IMAGE) {
- return item;
- }
- if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
- && !item.hasHint(Slice.HINT_ACTIONS)
- && !item.hasHint(Slice.HINT_LIST_ITEM)
- && (item.getType() != SliceItem.TYPE_ACTION)) {
- SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
- if (icon != null) return icon;
- }
- }
- return null;
- }
-
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringArray(mHints);
@@ -405,6 +386,9 @@
final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
null, extras);
Bundle.setDefusable(res, true);
+ if (res == null) {
+ return null;
+ }
return res.getParcelable(SliceProvider.EXTRA_SLICE);
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index df87b45..da718dc 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -19,15 +19,19 @@
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Intent;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
+import android.os.UserHandle;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
@@ -143,9 +147,13 @@
@Override
public Bundle call(String method, String arg, Bundle extras) {
if (method.equals(METHOD_SLICE)) {
- getContext().enforceCallingPermission(permission.BIND_SLICE,
- "Slice binding requires the permission BIND_SLICE");
Uri uri = extras.getParcelable(EXTRA_BIND_URI);
+ if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
+ getContext().enforceUriPermission(uri, permission.BIND_SLICE,
+ permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ "Slice binding requires the permission BIND_SLICE");
+ }
Slice s = handleBindSlice(uri);
Bundle b = new Bundle();
@@ -156,27 +164,34 @@
}
private Slice handleBindSlice(Uri sliceUri) {
- Slice[] output = new Slice[1];
- CountDownLatch latch = new CountDownLatch(1);
- Handler mainHandler = new Handler(Looper.getMainLooper());
- mainHandler.post(() -> {
- ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
- try {
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectAll()
- .penaltyDeath()
- .build());
- output[0] = onBindSlice(sliceUri);
- } finally {
- StrictMode.setThreadPolicy(oldPolicy);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ return onBindSliceStrict(sliceUri);
+ } else {
+ CountDownLatch latch = new CountDownLatch(1);
+ Slice[] output = new Slice[1];
+ Handler.getMain().post(() -> {
+ output[0] = onBindSliceStrict(sliceUri);
latch.countDown();
+ });
+ try {
+ latch.await();
+ return output[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
}
- });
+ }
+ }
+
+ private Slice onBindSliceStrict(Uri sliceUri) {
+ ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
try {
- latch.await();
- return output[0];
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyDeath()
+ .build());
+ return onBindSlice(sliceUri);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
}
}
}
diff --git a/core/java/android/app/slice/SliceQuery.java b/core/java/android/app/slice/SliceQuery.java
index d1fe2c9..9943c49 100644
--- a/core/java/android/app/slice/SliceQuery.java
+++ b/core/java/android/app/slice/SliceQuery.java
@@ -35,6 +35,27 @@
/**
* @hide
*/
+ public static SliceItem getPrimaryIcon(Slice slice) {
+ for (SliceItem item : slice.getItems()) {
+ if (item.getType() == SliceItem.TYPE_IMAGE) {
+ return item;
+ }
+ if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+ && !item.hasHint(Slice.HINT_ACTIONS)
+ && !item.hasHint(Slice.HINT_LIST_ITEM)
+ && (item.getType() != SliceItem.TYPE_ACTION)) {
+ SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
+ if (icon != null) {
+ return icon;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) {
SliceItem ret = null;
while (ret == null && list.size() != 0) {
diff --git a/core/java/android/app/slice/views/SliceView.java b/core/java/android/app/slice/views/SliceView.java
deleted file mode 100644
index 32484fc..0000000
--- a/core/java/android/app/slice/views/SliceView.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.slice.views;
-
-import android.annotation.StringDef;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import java.util.List;
-
-/**
- * A view that can display a {@link Slice} in different {@link SliceMode}'s.
- *
- * @hide
- */
-public class SliceView extends LinearLayout {
-
- private static final String TAG = "SliceView";
-
- /**
- * @hide
- */
- public abstract static class SliceModeView extends FrameLayout {
-
- public SliceModeView(Context context) {
- super(context);
- }
-
- /**
- * @return the {@link SliceMode} of the slice being presented.
- */
- public abstract String getMode();
-
- /**
- * @param slice the slice to show in this view.
- */
- public abstract void setSlice(Slice slice);
- }
-
- /**
- * @hide
- */
- @StringDef({
- MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
- })
- public @interface SliceMode {}
-
- /**
- * Mode indicating this slice should be presented in small template format.
- */
- public static final String MODE_SMALL = "SLICE_SMALL";
- /**
- * Mode indicating this slice should be presented in large template format.
- */
- public static final String MODE_LARGE = "SLICE_LARGE";
- /**
- * Mode indicating this slice should be presented as an icon.
- */
- public static final String MODE_SHORTCUT = "SLICE_ICON";
-
- /**
- * Will select the type of slice binding based on size of the View. TODO: Put in some info about
- * that selection.
- */
- private static final String MODE_AUTO = "auto";
-
- private String mMode = MODE_AUTO;
- private SliceModeView mCurrentView;
- private final ActionRow mActions;
- private Slice mCurrentSlice;
- private boolean mShowActions = true;
-
- /**
- * Simple constructor to create a slice view from code.
- *
- * @param context The context the view is running in.
- */
- public SliceView(Context context) {
- super(context);
- setOrientation(LinearLayout.VERTICAL);
- mActions = new ActionRow(mContext, true);
- mActions.setBackground(new ColorDrawable(0xffeeeeee));
- mCurrentView = new LargeTemplateView(mContext);
- addView(mCurrentView);
- addView(mActions);
- }
-
- /**
- * @hide
- */
- public void bindSlice(Intent intent) {
- // TODO
- }
-
- /**
- * Binds this view to the {@link Slice} associated with the provided {@link Uri}.
- */
- public void bindSlice(Uri sliceUri) {
- validate(sliceUri);
- Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
- bindSlice(s);
- }
-
- /**
- * Binds this view to the provided {@link Slice}.
- */
- public void bindSlice(Slice slice) {
- mCurrentSlice = slice;
- if (mCurrentSlice != null) {
- reinflate();
- }
- }
-
- /**
- * Call to clean up the view.
- */
- public void unbindSlice() {
- mCurrentSlice = null;
- }
-
- /**
- * Set the {@link SliceMode} this view should present in.
- */
- public void setMode(@SliceMode String mode) {
- setMode(mode, false /* animate */);
- }
-
- /**
- * @hide
- */
- public void setMode(@SliceMode String mode, boolean animate) {
- if (animate) {
- Log.e(TAG, "Animation not supported yet");
- }
- mMode = mode;
- reinflate();
- }
-
- /**
- * @return the {@link SliceMode} this view is presenting in.
- */
- public @SliceMode String getMode() {
- if (mMode.equals(MODE_AUTO)) {
- return MODE_LARGE;
- }
- return mMode;
- }
-
- /**
- * @hide
- *
- * Whether this view should show a row of actions with it.
- */
- public void setShowActionRow(boolean show) {
- mShowActions = show;
- reinflate();
- }
-
- private SliceModeView createView(String mode) {
- switch (mode) {
- case MODE_SHORTCUT:
- return new ShortcutView(getContext());
- case MODE_SMALL:
- return new SmallTemplateView(getContext());
- }
- return new LargeTemplateView(getContext());
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- unbindSlice();
- }
-
- private void reinflate() {
- if (mCurrentSlice == null) {
- return;
- }
- // TODO: Smarter mapping here from one state to the next.
- SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
- List<SliceItem> items = mCurrentSlice.getItems();
- SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
- Slice.HINT_ACTIONS,
- Slice.HINT_ALT);
- String mode = getMode();
- if (!mode.equals(mCurrentView.getMode())) {
- removeAllViews();
- mCurrentView = createView(mode);
- addView(mCurrentView);
- addView(mActions);
- }
- if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
- mCurrentView.setVisibility(View.VISIBLE);
- mCurrentView.setSlice(mCurrentSlice);
- } else {
- mCurrentView.setVisibility(View.GONE);
- }
-
- boolean showActions = mShowActions && actionRow != null
- && !mode.equals(MODE_SHORTCUT);
- if (showActions) {
- mActions.setActions(actionRow, color);
- mActions.setVisibility(View.VISIBLE);
- } else {
- mActions.setVisibility(View.GONE);
- }
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- // TODO -- may need to rethink for AGSA
- if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
- requestDisallowInterceptTouchEvent(true);
- }
- return super.onInterceptTouchEvent(ev);
- }
-
- private static void validate(Uri sliceUri) {
- if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
- throw new RuntimeException("Invalid uri " + sliceUri);
- }
- if (sliceUri.getPathSegments().size() == 0) {
- throw new RuntimeException("Invalid uri " + sliceUri);
- }
- }
-}
diff --git a/core/java/android/app/slice/views/ActionRow.java b/core/java/android/app/slice/widget/ActionRow.java
similarity index 99%
rename from core/java/android/app/slice/views/ActionRow.java
rename to core/java/android/app/slice/widget/ActionRow.java
index c7d99f7..c96e6304 100644
--- a/core/java/android/app/slice/views/ActionRow.java
+++ b/core/java/android/app/slice/widget/ActionRow.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
diff --git a/core/java/android/app/slice/views/GridView.java b/core/java/android/app/slice/widget/GridView.java
similarity index 98%
rename from core/java/android/app/slice/views/GridView.java
rename to core/java/android/app/slice/widget/GridView.java
index 6f30c50..67a3c67 100644
--- a/core/java/android/app/slice/views/GridView.java
+++ b/core/java/android/app/slice/widget/GridView.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
-import android.app.slice.views.LargeSliceAdapter.SliceListView;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
diff --git a/core/java/android/app/slice/views/LargeSliceAdapter.java b/core/java/android/app/slice/widget/LargeSliceAdapter.java
similarity index 97%
rename from core/java/android/app/slice/views/LargeSliceAdapter.java
rename to core/java/android/app/slice/widget/LargeSliceAdapter.java
index 6794ff9..267fff6 100644
--- a/core/java/android/app/slice/views/LargeSliceAdapter.java
+++ b/core/java/android/app/slice/widget/LargeSliceAdapter.java
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceViewHolder;
+import android.app.slice.widget.LargeSliceAdapter.SliceViewHolder;
import android.content.Context;
import android.util.ArrayMap;
import android.view.LayoutInflater;
@@ -71,7 +71,7 @@
@Override
public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View v = inflateforType(viewType);
+ View v = inflateForType(viewType);
v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
return new SliceViewHolder(v);
}
@@ -104,7 +104,7 @@
}
}
- private View inflateforType(int viewType) {
+ private View inflateForType(int viewType) {
switch (viewType) {
case TYPE_REMOTE_VIEWS:
return new FrameLayout(mContext);
diff --git a/core/java/android/app/slice/views/LargeTemplateView.java b/core/java/android/app/slice/widget/LargeTemplateView.java
similarity index 89%
rename from core/java/android/app/slice/views/LargeTemplateView.java
rename to core/java/android/app/slice/widget/LargeTemplateView.java
index 9e22516..f45b2a8 100644
--- a/core/java/android/app/slice/views/LargeTemplateView.java
+++ b/core/java/android/app/slice/widget/LargeTemplateView.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
import android.app.slice.SliceQuery;
-import android.app.slice.views.SliceView.SliceModeView;
+import android.app.slice.widget.SliceView.SliceModeView;
import android.content.Context;
import android.util.TypedValue;
@@ -35,11 +35,13 @@
* @hide
*/
public class LargeTemplateView extends SliceModeView {
+
private final LargeSliceAdapter mAdapter;
private final RecyclerView mRecyclerView;
private final int mDefaultHeight;
private final int mMaxHeight;
private Slice mSlice;
+ private boolean mIsScrollable;
public LargeTemplateView(Context context) {
super(context);
@@ -49,9 +51,6 @@
mAdapter = new LargeSliceAdapter(context);
mRecyclerView.setAdapter(mAdapter);
addView(mRecyclerView);
- int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300,
- getResources().getDisplayMetrics());
- setLayoutParams(new LayoutParams(width, WRAP_CONTENT));
mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
getResources().getDisplayMetrics());
mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
@@ -68,7 +67,7 @@
mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mRecyclerView.getMeasuredHeight() > mMaxHeight
- || mSlice.hasHint(Slice.HINT_PARTIAL)) {
+ || (mSlice != null && mSlice.hasHint(Slice.HINT_PARTIAL))) {
mRecyclerView.getLayoutParams().height = mDefaultHeight;
} else {
mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
@@ -112,4 +111,12 @@
sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
items.addAll(sliceItems);
}
+
+ /**
+ * Whether or not the content in this template should be scrollable.
+ */
+ public void setScrollable(boolean isScrollable) {
+ // TODO -- restrict / enable how much this view can show
+ mIsScrollable = isScrollable;
+ }
}
diff --git a/core/java/android/app/slice/views/MessageView.java b/core/java/android/app/slice/widget/MessageView.java
similarity index 96%
rename from core/java/android/app/slice/views/MessageView.java
rename to core/java/android/app/slice/widget/MessageView.java
index 77252bf2..3124398 100644
--- a/core/java/android/app/slice/views/MessageView.java
+++ b/core/java/android/app/slice/widget/MessageView.java
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceListView;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
diff --git a/core/java/android/app/slice/views/RemoteInputView.java b/core/java/android/app/slice/widget/RemoteInputView.java
similarity index 99%
rename from core/java/android/app/slice/views/RemoteInputView.java
rename to core/java/android/app/slice/widget/RemoteInputView.java
index e53cb1e..6eff5af 100644
--- a/core/java/android/app/slice/views/RemoteInputView.java
+++ b/core/java/android/app/slice/widget/RemoteInputView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.animation.Animator;
import android.app.Notification;
diff --git a/core/java/android/app/slice/views/ShortcutView.java b/core/java/android/app/slice/widget/ShortcutView.java
similarity index 90%
rename from core/java/android/app/slice/views/ShortcutView.java
rename to core/java/android/app/slice/widget/ShortcutView.java
index b6790c7..0bca8ce 100644
--- a/core/java/android/app/slice/views/ShortcutView.java
+++ b/core/java/android/app/slice/widget/ShortcutView.java
@@ -14,21 +14,20 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
import android.app.slice.SliceQuery;
-import android.app.slice.views.SliceView.SliceModeView;
+import android.app.slice.widget.SliceView.SliceModeView;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.net.Uri;
-import android.view.ViewGroup;
import com.android.internal.R;
@@ -46,17 +45,14 @@
public ShortcutView(Context context) {
super(context);
- mLargeIconSize = getContext().getResources()
- .getDimensionPixelSize(R.dimen.slice_shortcut_size);
mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
- setLayoutParams(new ViewGroup.LayoutParams(mLargeIconSize, mLargeIconSize));
}
@Override
public void setSlice(Slice slice) {
removeAllViews();
SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
- SliceItem iconItem = slice.getPrimaryIcon();
+ SliceItem iconItem = SliceQuery.getPrimaryIcon(slice);
SliceItem textItem = sliceItem != null
? SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT)
: SliceQuery.find(slice, SliceItem.TYPE_TEXT);
diff --git a/core/java/android/app/slice/widget/SliceView.java b/core/java/android/app/slice/widget/SliceView.java
new file mode 100644
index 0000000..5bafbc0
--- /dev/null
+++ b/core/java/android/app/slice/widget/SliceView.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.slice.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * A view for displaying a {@link Slice} which is a piece of app content and actions. SliceView is
+ * able to present slice content in a templated format outside of the associated app. The way this
+ * content is displayed depends on the structure of the slice, the hints associated with the
+ * content, and the mode that SliceView is configured for. The modes that SliceView supports are:
+ * <ul>
+ * <li><b>Shortcut</b>: A shortcut is presented as an icon and a text label representing the main
+ * content or action associated with the slice.</li>
+ * <li><b>Small</b>: The small format has a restricted height and can present a single
+ * {@link SliceItem} or a limited collection of items.</li>
+ * <li><b>Large</b>: The large format displays multiple small templates in a list, if scrolling is
+ * not enabled (see {@link #setScrollable(boolean)}) the view will show as many items as it can
+ * comfortably fit.</li>
+ * </ul>
+ * <p>
+ * When constructing a slice, the contents of it can be annotated with hints, these provide the OS
+ * with some information on how the content should be displayed. For example, text annotated with
+ * {@link Slice#HINT_TITLE} would be placed in the title position of a template. A slice annotated
+ * with {@link Slice#HINT_LIST} would present the child items of that slice in a list.
+ * <p>
+ * SliceView can be provided a slice via a uri {@link #setSlice(Uri)} in which case a content
+ * observer will be set for that uri and the view will update if there are any changes to the slice.
+ * To use this the app must have a special permission to bind to the slice (see
+ * {@link android.Manifest.permission#BIND_SLICE}).
+ * <p>
+ * Example usage:
+ *
+ * <pre class="prettyprint">
+ * SliceView v = new SliceView(getContext());
+ * v.setMode(desiredMode);
+ * v.setSlice(sliceUri);
+ * </pre>
+ */
+public class SliceView extends ViewGroup {
+
+ private static final String TAG = "SliceView";
+
+ /**
+ * @hide
+ */
+ public abstract static class SliceModeView extends FrameLayout {
+
+ public SliceModeView(Context context) {
+ super(context);
+ }
+
+ /**
+ * @return the mode of the slice being presented.
+ */
+ public abstract String getMode();
+
+ /**
+ * @param slice the slice to show in this view.
+ */
+ public abstract void setSlice(Slice slice);
+ }
+
+ /**
+ * @hide
+ */
+ @StringDef({
+ MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
+ })
+ public @interface SliceMode {}
+
+ /**
+ * Mode indicating this slice should be presented in small template format.
+ */
+ public static final String MODE_SMALL = "SLICE_SMALL";
+ /**
+ * Mode indicating this slice should be presented in large template format.
+ */
+ public static final String MODE_LARGE = "SLICE_LARGE";
+ /**
+ * Mode indicating this slice should be presented as an icon.
+ */
+ public static final String MODE_SHORTCUT = "SLICE_ICON";
+
+ /**
+ * Will select the type of slice binding based on size of the View. TODO: Put in some info about
+ * that selection.
+ */
+ private static final String MODE_AUTO = "auto";
+
+ private String mMode = MODE_AUTO;
+ private SliceModeView mCurrentView;
+ private final ActionRow mActions;
+ private Slice mCurrentSlice;
+ private boolean mShowActions = true;
+ private boolean mIsScrollable;
+ private SliceObserver mObserver;
+ private final int mShortcutSize;
+
+ public SliceView(Context context) {
+ this(context, null);
+ }
+
+ public SliceView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SliceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
+ mActions = new ActionRow(mContext, true);
+ mActions.setBackground(new ColorDrawable(0xffeeeeee));
+ mCurrentView = new LargeTemplateView(mContext);
+ addView(mCurrentView, getChildLp(mCurrentView));
+ addView(mActions, getChildLp(mActions));
+ mShortcutSize = getContext().getResources()
+ .getDimensionPixelSize(R.dimen.slice_shortcut_size);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+ int actionHeight = mActions.getVisibility() != View.GONE
+ ? mActions.getMeasuredHeight()
+ : 0;
+ int newHeightSpec = MeasureSpec.makeMeasureSpec(
+ mCurrentView.getMeasuredHeight() + actionHeight, MeasureSpec.EXACTLY);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ setMeasuredDimension(width, newHeightSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ mCurrentView.layout(l, t, l + mCurrentView.getMeasuredWidth(),
+ t + mCurrentView.getMeasuredHeight());
+ if (mActions.getVisibility() != View.GONE) {
+ mActions.layout(l, mCurrentView.getMeasuredHeight(), l + mActions.getMeasuredWidth(),
+ mCurrentView.getMeasuredHeight() + mActions.getMeasuredHeight());
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void showSlice(Intent intent) {
+ // TODO
+ }
+
+ /**
+ * Populates this view with the {@link Slice} associated with the provided {@link Uri}. To use
+ * this method your app must have the permission
+ * {@link android.Manifest.permission#BIND_SLICE}).
+ * <p>
+ * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
+ * updated when the slice identified by the provided URI changes. The lifecycle of this observer
+ * is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}.
+ * To unregister this observer outside of that you can call {@link #clearSlice}.
+ *
+ * @return true if the a slice was found for the provided uri.
+ * @see #clearSlice
+ */
+ public boolean setSlice(@NonNull Uri sliceUri) {
+ Preconditions.checkNotNull(sliceUri,
+ "Uri cannot be null, to remove the slice use clearSlice()");
+ if (sliceUri == null) {
+ clearSlice();
+ return false;
+ }
+ validate(sliceUri);
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
+ if (s != null) {
+ mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
+ if (isAttachedToWindow()) {
+ registerSlice(sliceUri);
+ }
+ showSlice(s);
+ }
+ return s != null;
+ }
+
+ /**
+ * Populates this view to the provided {@link Slice}.
+ * <p>
+ * This does not register a content observer on the URI that the slice is backed by so it will
+ * not update if the content changes. To have the view update when the content changes use
+ * {@link #setSlice(Uri)} instead. Unlike {@link #setSlice(Uri)}, this method does not require
+ * any special permissions.
+ */
+ public void showSlice(@NonNull Slice slice) {
+ Preconditions.checkNotNull(slice,
+ "Slice cannot be null, to remove the slice use clearSlice()");
+ clearSlice();
+ mCurrentSlice = slice;
+ reinflate();
+ }
+
+ /**
+ * Unregisters the change observer that is set when using {@link #setSlice}. Normally this is
+ * done automatically during {@link #onDetachedFromWindow()}.
+ * <p>
+ * It is safe to call this method multiple times.
+ */
+ public void clearSlice() {
+ mCurrentSlice = null;
+ if (mObserver != null) {
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ mObserver = null;
+ }
+ }
+
+ /**
+ * Set the mode this view should present in.
+ */
+ public void setMode(@SliceMode String mode) {
+ setMode(mode, false /* animate */);
+ }
+
+ /**
+ * Set whether this view should allow scrollable content when presenting in {@link #MODE_LARGE}.
+ */
+ public void setScrollable(boolean isScrollable) {
+ mIsScrollable = isScrollable;
+ reinflate();
+ }
+
+ /**
+ * @hide
+ */
+ public void setMode(@SliceMode String mode, boolean animate) {
+ if (animate) {
+ Log.e(TAG, "Animation not supported yet");
+ }
+ mMode = mode;
+ reinflate();
+ }
+
+ /**
+ * @return the mode this view is presenting in.
+ */
+ public @SliceMode String getMode() {
+ if (mMode.equals(MODE_AUTO)) {
+ return MODE_LARGE;
+ }
+ return mMode;
+ }
+
+ /**
+ * @hide
+ *
+ * Whether this view should show a row of actions with it.
+ */
+ public void setShowActionRow(boolean show) {
+ mShowActions = show;
+ reinflate();
+ }
+
+ private SliceModeView createView(String mode) {
+ switch (mode) {
+ case MODE_SHORTCUT:
+ return new ShortcutView(getContext());
+ case MODE_SMALL:
+ return new SmallTemplateView(getContext());
+ }
+ return new LargeTemplateView(getContext());
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ registerSlice(mCurrentSlice != null ? mCurrentSlice.getUri() : null);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mObserver != null) {
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ mObserver = null;
+ }
+ }
+
+ private void registerSlice(Uri sliceUri) {
+ if (sliceUri == null || mObserver == null) {
+ return;
+ }
+ mContext.getContentResolver().registerContentObserver(sliceUri,
+ false /* notifyForDescendants */, mObserver);
+ }
+
+ private void reinflate() {
+ if (mCurrentSlice == null) {
+ return;
+ }
+ // TODO: Smarter mapping here from one state to the next.
+ SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
+ List<SliceItem> items = mCurrentSlice.getItems();
+ SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
+ Slice.HINT_ACTIONS,
+ Slice.HINT_ALT);
+ String mode = getMode();
+ if (!mode.equals(mCurrentView.getMode())) {
+ removeAllViews();
+ mCurrentView = createView(mode);
+ addView(mCurrentView, getChildLp(mCurrentView));
+ addView(mActions, getChildLp(mActions));
+ }
+ if (mode.equals(MODE_LARGE)) {
+ ((LargeTemplateView) mCurrentView).setScrollable(mIsScrollable);
+ }
+ if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
+ mCurrentView.setVisibility(View.VISIBLE);
+ mCurrentView.setSlice(mCurrentSlice);
+ } else {
+ mCurrentView.setVisibility(View.GONE);
+ }
+
+ boolean showActions = mShowActions && actionRow != null
+ && !mode.equals(MODE_SHORTCUT);
+ if (showActions) {
+ mActions.setActions(actionRow, color);
+ mActions.setVisibility(View.VISIBLE);
+ } else {
+ mActions.setVisibility(View.GONE);
+ }
+ }
+
+ private LayoutParams getChildLp(View child) {
+ if (child instanceof ShortcutView) {
+ return new LayoutParams(mShortcutSize, mShortcutSize);
+ } else {
+ return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+ }
+
+ private static void validate(Uri sliceUri) {
+ if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
+ throw new RuntimeException("Invalid uri " + sliceUri);
+ }
+ if (sliceUri.getPathSegments().size() == 0) {
+ throw new RuntimeException("Invalid uri " + sliceUri);
+ }
+ }
+
+ private class SliceObserver extends ContentObserver {
+ SliceObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ this.onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), uri);
+ mCurrentSlice = s;
+ reinflate();
+ }
+ }
+}
diff --git a/core/java/android/app/slice/views/SliceViewUtil.java b/core/java/android/app/slice/widget/SliceViewUtil.java
similarity index 99%
rename from core/java/android/app/slice/views/SliceViewUtil.java
rename to core/java/android/app/slice/widget/SliceViewUtil.java
index 19e8e7c..0366998 100644
--- a/core/java/android/app/slice/views/SliceViewUtil.java
+++ b/core/java/android/app/slice/widget/SliceViewUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.annotation.ColorInt;
import android.content.Context;
diff --git a/core/java/android/app/slice/views/SmallTemplateView.java b/core/java/android/app/slice/widget/SmallTemplateView.java
similarity index 97%
rename from core/java/android/app/slice/views/SmallTemplateView.java
rename to core/java/android/app/slice/widget/SmallTemplateView.java
index 42b2d21..1c4c5df 100644
--- a/core/java/android/app/slice/views/SmallTemplateView.java
+++ b/core/java/android/app/slice/widget/SmallTemplateView.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.app.PendingIntent.CanceledException;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceListView;
-import android.app.slice.views.SliceView.SliceModeView;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
+import android.app.slice.widget.SliceView.SliceModeView;
import android.content.Context;
import android.os.AsyncTask;
import android.view.View;
diff --git a/core/java/android/app/usage/AppStandby.java b/core/java/android/app/usage/AppStandby.java
new file mode 100644
index 0000000..6f9fc2f
--- /dev/null
+++ b/core/java/android/app/usage/AppStandby.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.usage;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Set of constants for app standby buckets and reasons. Apps will be moved into different buckets
+ * that affect how frequently they can run in the background or perform other battery-consuming
+ * actions. Buckets will be assigned based on how frequently or when the system thinks the user
+ * is likely to use the app.
+ * @hide
+ */
+public class AppStandby {
+
+ /** The app was used very recently, currently in use or likely to be used very soon. */
+ public static final int STANDBY_BUCKET_ACTIVE = 0;
+
+ // Leave some gap in case we want to increase the number of buckets
+
+ /** The app was used recently and/or likely to be used in the next few hours */
+ public static final int STANDBY_BUCKET_WORKING_SET = 3;
+
+ // Leave some gap in case we want to increase the number of buckets
+
+ /** The app was used in the last few days and/or likely to be used in the next few days */
+ public static final int STANDBY_BUCKET_FREQUENT = 6;
+
+ // Leave some gap in case we want to increase the number of buckets
+
+ /** The app has not be used for several days and/or is unlikely to be used for several days */
+ public static final int STANDBY_BUCKET_RARE = 9;
+
+ // Leave some gap in case we want to increase the number of buckets
+
+ /** The app has never been used. */
+ public static final int STANDBY_BUCKET_NEVER = 12;
+
+ /** Reason for bucketing -- default initial state */
+ public static final String REASON_DEFAULT = "default";
+
+ /** Reason for bucketing -- timeout */
+ public static final String REASON_TIMEOUT = "timeout";
+
+ /** Reason for bucketing -- usage */
+ public static final String REASON_USAGE = "usage";
+
+ /** Reason for bucketing -- forced by user / shell command */
+ public static final String REASON_FORCED = "forced";
+
+ /**
+ * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will
+ * be appended.
+ */
+ public static final String REASON_PREDICTED = "predicted";
+
+ @IntDef(flag = false, value = {
+ STANDBY_BUCKET_ACTIVE,
+ STANDBY_BUCKET_WORKING_SET,
+ STANDBY_BUCKET_FREQUENT,
+ STANDBY_BUCKET_RARE,
+ STANDBY_BUCKET_NEVER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StandbyBuckets {}
+}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 31b2359..4fbbdf2 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -36,4 +36,6 @@
void onCarrierPrivilegedAppsChanged();
void reportChooserSelection(String packageName, int userId, String contentType,
in String[] annotations, String action);
+ int getAppStandbyBucket(String packageName, String callingPackage, int userId);
+ void setAppStandbyBucket(String packageName, int bucket, int userId);
}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index fd579fc..3a3e16e 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -19,6 +19,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.app.usage.AppStandby.StandbyBuckets;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
@@ -247,6 +248,32 @@
}
/**
+ * @hide
+ */
+ public @StandbyBuckets int getAppStandbyBucket(String packageName) {
+ try {
+ return mService.getAppStandbyBucket(packageName, mContext.getOpPackageName(),
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ }
+ return AppStandby.STANDBY_BUCKET_ACTIVE;
+ }
+
+ /**
+ * @hide
+ * Changes the app standby state to the provided bucket.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE)
+ public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) {
+ try {
+ mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId());
+ } catch (RemoteException e) {
+ // Nothing to do
+ }
+ }
+
+ /**
* {@hide}
* Temporarily whitelist the specified app for a short duration. This is to allow an app
* receiving a high priority message to be able to access the network and acquire wakelocks
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index dc9970a..ab0eb92 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -19,8 +19,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Color;
@@ -66,11 +64,8 @@
// When we're inflating the initialLayout for a AppWidget, we only allow
// views that are allowed in RemoteViews.
- static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
- public boolean onLoadClass(Class clazz) {
- return clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
- }
- };
+ private static final LayoutInflater.Filter INFLATER_FILTER =
+ (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
Context mContext;
Context mRemoteContext;
@@ -136,13 +131,19 @@
mAppWidgetId = appWidgetId;
mInfo = info;
+ // We add padding to the AppWidgetHostView if necessary
+ Rect padding = getDefaultPadding();
+ setPadding(padding.left, padding.top, padding.right, padding.bottom);
+
// Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for
// a widget, eg. for some widgets in safe mode.
if (info != null) {
- // We add padding to the AppWidgetHostView if necessary
- Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null);
- setPadding(padding.left, padding.top, padding.right, padding.bottom);
- updateContentDescription(info);
+ String description = info.loadLabel(getContext().getPackageManager());
+ if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
+ description = Resources.getSystem().getString(
+ com.android.internal.R.string.suspended_widget_accessibility, description);
+ }
+ setContentDescription(description);
}
}
@@ -164,23 +165,23 @@
*/
public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
Rect padding) {
- PackageManager packageManager = context.getPackageManager();
- ApplicationInfo appInfo;
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = context.getPackageManager().getApplicationInfo(component.getPackageName(), 0);
+ } catch (NameNotFoundException e) {
+ // if we can't find the package, ignore
+ }
+ return getDefaultPaddingForWidget(context, appInfo, padding);
+ }
+ private static Rect getDefaultPaddingForWidget(Context context, ApplicationInfo appInfo,
+ Rect padding) {
if (padding == null) {
padding = new Rect(0, 0, 0, 0);
} else {
padding.set(0, 0, 0, 0);
}
-
- try {
- appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0);
- } catch (NameNotFoundException e) {
- // if we can't find the package, return 0 padding
- return padding;
- }
-
- if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (appInfo != null && appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
Resources r = context.getResources();
padding.left = r.getDimensionPixelSize(com.android.internal.
R.dimen.default_app_widget_padding_left);
@@ -194,6 +195,11 @@
return padding;
}
+ private Rect getDefaultPadding() {
+ return getDefaultPaddingForWidget(mContext,
+ mInfo == null ? null : mInfo.providerInfo.applicationInfo, null);
+ }
+
public int getAppWidgetId() {
return mAppWidgetId;
}
@@ -284,10 +290,7 @@
newOptions = new Bundle();
}
- Rect padding = new Rect();
- if (mInfo != null) {
- padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding);
- }
+ Rect padding = getDefaultPadding();
float density = getResources().getDisplayMetrics().density;
int xPaddingDips = (int) ((padding.left + padding.right) / density);
@@ -361,7 +364,7 @@
* initial layout.
*/
void resetAppWidget(AppWidgetProviderInfo info) {
- mInfo = info;
+ setAppWidget(mAppWidgetId, info);
mViewMode = VIEW_MODE_NOINIT;
updateAppWidget(null);
}
@@ -433,7 +436,6 @@
}
applyContent(content, recycled, exception);
- updateContentDescription(mInfo);
}
private void applyContent(View content, boolean recycled, Exception exception) {
@@ -460,27 +462,6 @@
}
}
- private void updateContentDescription(AppWidgetProviderInfo info) {
- if (info != null) {
- LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
- ApplicationInfo appInfo = null;
- try {
- appInfo = launcherApps.getApplicationInfo(
- info.provider.getPackageName(), 0, info.getProfile());
- } catch (NameNotFoundException e) {
- // ignore -- use null.
- }
- if (appInfo != null &&
- (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
- setContentDescription(
- Resources.getSystem().getString(
- com.android.internal.R.string.suspended_widget_accessibility, info.label));
- } else {
- setContentDescription(info.label);
- }
- }
- }
-
private void inflateAsync(RemoteViews remoteViews) {
// Prepare a local reference to the remote Context so we're ready to
// inflate any requested LayoutParams.
@@ -614,7 +595,7 @@
LayoutInflater inflater = (LayoutInflater)
theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater = inflater.cloneInContext(theirContext);
- inflater.setFilter(sInflaterFilter);
+ inflater.setFilter(INFLATER_FILTER);
AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 3526e18..578a5b8 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1134,8 +1134,32 @@
}
/**
- * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of
- * the local Bluetooth adapter.
+ * Returns the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth
+ * adapter.
+ *
+ * @return {@link BluetoothClass} Bluetooth CoD of local Bluetooth device.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ public BluetoothClass getBluetoothClass() {
+ if (getState() != STATE_ON) return null;
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) return mService.getBluetoothClass();
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth
+ * adapter.
+ *
+ * <p>Note: This value persists across system reboot.
*
* @param bluetoothClass {@link BluetoothClass} to set the local Bluetooth adapter to.
* @return true if successful, false if unsuccessful.
@@ -2104,8 +2128,8 @@
} else if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener);
return true;
- } else if (profile == BluetoothProfile.INPUT_DEVICE) {
- BluetoothInputDevice iDev = new BluetoothInputDevice(context, listener);
+ } else if (profile == BluetoothProfile.HID_HOST) {
+ BluetoothHidHost iDev = new BluetoothHidHost(context, listener);
return true;
} else if (profile == BluetoothProfile.PAN) {
BluetoothPan pan = new BluetoothPan(context, listener);
@@ -2128,8 +2152,8 @@
} else if (profile == BluetoothProfile.MAP_CLIENT) {
BluetoothMapClient mapClient = new BluetoothMapClient(context, listener);
return true;
- } else if (profile == BluetoothProfile.INPUT_HOST) {
- BluetoothInputHost iHost = new BluetoothInputHost(context, listener);
+ } else if (profile == BluetoothProfile.HID_DEVICE) {
+ BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener);
return true;
} else {
return false;
@@ -2167,8 +2191,8 @@
BluetoothAvrcpController avrcp = (BluetoothAvrcpController) proxy;
avrcp.close();
break;
- case BluetoothProfile.INPUT_DEVICE:
- BluetoothInputDevice iDev = (BluetoothInputDevice) proxy;
+ case BluetoothProfile.HID_HOST:
+ BluetoothHidHost iDev = (BluetoothHidHost) proxy;
iDev.close();
break;
case BluetoothProfile.PAN:
@@ -2207,9 +2231,9 @@
BluetoothMapClient mapClient = (BluetoothMapClient) proxy;
mapClient.close();
break;
- case BluetoothProfile.INPUT_HOST:
- BluetoothInputHost iHost = (BluetoothInputHost) proxy;
- iHost.close();
+ case BluetoothProfile.HID_DEVICE:
+ BluetoothHidDevice hidDevice = (BluetoothHidDevice) proxy;
+ hidDevice.close();
break;
}
}
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index 2c12403..243ad35 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -120,7 +120,7 @@
public static final int WRITE_TYPE_DEFAULT = 0x02;
/**
- * Wrtite characteristic without requiring a response by the remote device
+ * Write characteristic without requiring a response by the remote device
*/
public static final int WRITE_TYPE_NO_RESPONSE = 0x01;
diff --git a/core/java/android/bluetooth/BluetoothInputHost.java b/core/java/android/bluetooth/BluetoothHidDevice.java
similarity index 85%
rename from core/java/android/bluetooth/BluetoothInputHost.java
rename to core/java/android/bluetooth/BluetoothHidDevice.java
index e18d9d1..e3d763a 100644
--- a/core/java/android/bluetooth/BluetoothInputHost.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -31,11 +31,18 @@
import java.util.List;
/**
- * @hide
+ * Provides the public APIs to control the Bluetooth HID Device
+ * profile.
+ *
+ * BluetoothHidDevice is a proxy object for controlling the Bluetooth HID
+ * Device Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHidDevice proxy object.
+ *
+ * {@hide}
*/
-public final class BluetoothInputHost implements BluetoothProfile {
+public final class BluetoothHidDevice implements BluetoothProfile {
- private static final String TAG = BluetoothInputHost.class.getSimpleName();
+ private static final String TAG = BluetoothHidDevice.class.getSimpleName();
/**
* Intent used to broadcast the change in connection state of the Input
@@ -57,12 +64,14 @@
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
- "android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED";
+ "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
/**
* Constants representing device subclass.
*
- * @see #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)
+ * @see #registerApp
+ * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+ * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)
*/
public static final byte SUBCLASS1_NONE = (byte) 0x00;
public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
@@ -80,9 +89,9 @@
/**
* Constants representing report types.
*
- * @see BluetoothHidDeviceCallback#onGetReport(byte, byte, int)
- * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
- * @see BluetoothHidDeviceCallback#onIntrData(byte, byte[])
+ * @see BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)
+ * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
+ * @see BluetoothHidDeviceCallback#onIntrData(BluetoothDevice, byte, byte[])
*/
public static final byte REPORT_TYPE_INPUT = (byte) 1;
public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
@@ -91,7 +100,7 @@
/**
* Constants representing error response for Set Report.
*
- * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
+ * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
*/
public static final byte ERROR_RSP_SUCCESS = (byte) 0;
public static final byte ERROR_RSP_NOT_READY = (byte) 1;
@@ -104,7 +113,7 @@
* Constants representing protocol mode used set by host. Default is always
* {@link #PROTOCOL_REPORT_MODE} unless notified otherwise.
*
- * @see BluetoothHidDeviceCallback#onSetProtocol(byte)
+ * @see BluetoothHidDeviceCallback#onSetProtocol(BluetoothDevice, byte)
*/
public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
@@ -113,7 +122,7 @@
private ServiceListener mServiceListener;
- private volatile IBluetoothInputHost mService;
+ private volatile IBluetoothHidDevice mService;
private BluetoothAdapter mAdapter;
@@ -169,18 +178,7 @@
public void onBluetoothStateChange(boolean up) {
Log.d(TAG, "onBluetoothStateChange: up=" + up);
synchronized (mConnection) {
- if (!up) {
- Log.d(TAG, "Unbinding service...");
- if (mService != null) {
- mService = null;
- try {
- mContext.unbindService(mConnection);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "onBluetoothStateChange: could not unbind service:",
- e);
- }
- }
- } else {
+ if (up) {
try {
if (mService == null) {
Log.d(TAG, "Binding HID Device service...");
@@ -189,14 +187,15 @@
} catch (IllegalStateException e) {
Log.e(TAG,
"onBluetoothStateChange: could not bind to HID Dev "
- + "service: ",
- e);
+ + "service: ", e);
} catch (SecurityException e) {
Log.e(TAG,
"onBluetoothStateChange: could not bind to HID Dev "
- + "service: ",
- e);
+ + "service: ", e);
}
+ } else {
+ Log.d(TAG, "Unbinding service...");
+ doUnbind();
}
}
}
@@ -205,23 +204,23 @@
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
Log.d(TAG, "onServiceConnected()");
- mService = IBluetoothInputHost.Stub.asInterface(service);
+ mService = IBluetoothHidDevice.Stub.asInterface(service);
if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST,
- BluetoothInputHost.this);
+ mServiceListener.onServiceConnected(BluetoothProfile.HID_DEVICE,
+ BluetoothHidDevice.this);
}
}
public void onServiceDisconnected(ComponentName className) {
Log.d(TAG, "onServiceDisconnected()");
mService = null;
if (mServiceListener != null) {
- mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST);
+ mServiceListener.onServiceDisconnected(BluetoothProfile.HID_DEVICE);
}
}
};
- BluetoothInputHost(Context context, ServiceListener listener) {
- Log.v(TAG, "BluetoothInputHost");
+ BluetoothHidDevice(Context context, ServiceListener listener) {
+ Log.v(TAG, "BluetoothHidDevice");
mContext = context;
mServiceListener = listener;
@@ -240,7 +239,7 @@
}
boolean doBind() {
- Intent intent = new Intent(IBluetoothInputHost.class.getName());
+ Intent intent = new Intent(IBluetoothHidDevice.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
@@ -252,6 +251,18 @@
return true;
}
+ void doUnbind() {
+ Log.d(TAG, "Unbinding HidDevService");
+ if (mService != null) {
+ mService = null;
+ try {
+ mContext.unbindService(mConnection);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Unable to unbind HidDevService", e);
+ }
+ }
+ }
+
void close() {
Log.v(TAG, "close()");
@@ -265,16 +276,8 @@
}
synchronized (mConnection) {
- if (mService != null) {
- mService = null;
- try {
- mContext.unbindService(mConnection);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "close: could not unbind HID Dev service: ", e);
- }
- }
+ doUnbind();
}
-
mServiceListener = null;
}
@@ -285,7 +288,7 @@
public List<BluetoothDevice> getConnectedDevices() {
Log.v(TAG, "getConnectedDevices()");
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
return service.getConnectedDevices();
@@ -306,7 +309,7 @@
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
return service.getDevicesMatchingConnectionStates(states);
@@ -327,7 +330,7 @@
public int getConnectionState(BluetoothDevice device) {
Log.v(TAG, "getConnectionState(): device=" + device);
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
return service.getConnectionState(device);
@@ -367,7 +370,7 @@
return false;
}
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
BluetoothHidDeviceAppConfiguration config =
@@ -388,7 +391,9 @@
/**
* Unregisters application. Active connection will be disconnected and no
* new connections will be allowed until registered again using
- * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
+ * {@link #registerApp
+ * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+ * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)}
*
* @param config {@link BluetoothHidDeviceAppConfiguration} object as obtained from {@link
* BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice,
@@ -401,7 +406,7 @@
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.unregisterApp(config);
@@ -426,7 +431,7 @@
public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.sendReport(device, id, data);
@@ -454,7 +459,7 @@
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.replyReport(device, type, id, data);
@@ -480,7 +485,7 @@
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.reportError(device, error);
@@ -504,7 +509,7 @@
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.unplug(device);
@@ -529,7 +534,7 @@
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.connect(device);
@@ -553,7 +558,7 @@
boolean result = false;
- final IBluetoothInputHost service = mService;
+ final IBluetoothHidDevice service = mService;
if (service != null) {
try {
result = service.disconnect(device);
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
index 2731935..d1efa2d 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
@@ -21,7 +21,16 @@
import java.util.Random;
-/** @hide */
+/**
+ * Represents the app configuration for a Bluetooth HID Device application.
+ *
+ * The app needs a BluetoothHidDeviceAppConfiguration token to unregister
+ * the Bluetooth HID Device service.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
public final class BluetoothHidDeviceAppConfiguration implements Parcelable {
private final long mHash;
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
index 1f80ed7..ccc3ef4 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
@@ -19,7 +19,17 @@
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device
+ * application.
+ *
+ * The BluetoothHidDevice framework will update the L2CAP QoS settings for the
+ * app during registration.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
public final int serviceType;
@@ -36,8 +46,7 @@
public static final int MAX = (int) 0xffffffff;
public BluetoothHidDeviceAppQosSettings(int serviceType, int tokenRate, int tokenBucketSize,
- int peakBandwidth,
- int latency, int delayVariation) {
+ int peakBandwidth, int latency, int delayVariation) {
this.serviceType = serviceType;
this.tokenRate = tokenRate;
this.tokenBucketSize = tokenBucketSize;
@@ -66,10 +75,13 @@
@Override
public BluetoothHidDeviceAppQosSettings createFromParcel(Parcel in) {
- return new BluetoothHidDeviceAppQosSettings(in.readInt(), in.readInt(),
+ return new BluetoothHidDeviceAppQosSettings(
in.readInt(),
in.readInt(),
- in.readInt(), in.readInt());
+ in.readInt(),
+ in.readInt(),
+ in.readInt(),
+ in.readInt());
}
@Override
@@ -90,7 +102,7 @@
/** @return an int array representation of this instance */
public int[] toArray() {
- return new int[]{
+ return new int[] {
serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation
};
}
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
index d21d506..f01c493 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -19,7 +19,18 @@
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth
+ * HID Device application.
+ *
+ * The BluetoothHidDevice framework adds the SDP record during app
+ * registration, so that the Android device can be discovered as a Bluetooth
+ * HID Device.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
public final String name;
@@ -57,8 +68,12 @@
@Override
public BluetoothHidDeviceAppSdpSettings createFromParcel(Parcel in) {
- return new BluetoothHidDeviceAppSdpSettings(in.readString(), in.readString(),
- in.readString(), in.readByte(), in.createByteArray());
+ return new BluetoothHidDeviceAppSdpSettings(
+ in.readString(),
+ in.readString(),
+ in.readString(),
+ in.readByte(),
+ in.createByteArray());
}
@Override
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
index 3d407a6..5ccda0d 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -18,16 +18,24 @@
import android.util.Log;
-/** @hide */
+/**
+ * The template class that applications use to call callback functions on
+ * events from the HID host. Callback functions are wrapped in this class and
+ * registered to the Android system during app registration.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
public abstract class BluetoothHidDeviceCallback {
- private static final String TAG = BluetoothHidDeviceCallback.class.getSimpleName();
+ private static final String TAG = "BluetoothHidDevCallback";
/**
* Callback called when application registration state changes. Usually it's
* called due to either
- * {@link BluetoothHidDevice#registerApp(String, String, String, byte, byte[],
- * BluetoothHidDeviceCallback)}
+ * {@link BluetoothHidDevice#registerApp
+ * (String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
* or
* {@link BluetoothHidDevice#unregisterApp(BluetoothHidDeviceAppConfiguration)}
* , but can be also unsolicited in case e.g. Bluetooth was turned off in
@@ -79,7 +87,7 @@
/**
* Callback called when SET_REPORT is received from remote host. In case
* received data are invalid, application shall respond with
- * {@link BluetoothHidDevice#reportError(BluetoothDevice)}.
+ * {@link BluetoothHidDevice#reportError(BluetoothDevice, byte)}.
*
* @param type Report Type.
* @param id Report Id.
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothHidHost.java
similarity index 91%
rename from core/java/android/bluetooth/BluetoothInputDevice.java
rename to core/java/android/bluetooth/BluetoothHidHost.java
index 3261576..8ad0f9d 100644
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -35,16 +35,16 @@
* This class provides the public APIs to control the Bluetooth Input
* Device Profile.
*
- * <p>BluetoothInputDevice is a proxy object for controlling the Bluetooth
+ * <p>BluetoothHidHost is a proxy object for controlling the Bluetooth
* Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
- * the BluetoothInputDevice proxy object.
+ * the BluetoothHidHost proxy object.
*
* <p>Each method is protected with its appropriate permission.
*
* @hide
*/
-public final class BluetoothInputDevice implements BluetoothProfile {
- private static final String TAG = "BluetoothInputDevice";
+public final class BluetoothHidHost implements BluetoothProfile {
+ private static final String TAG = "BluetoothHidHost";
private static final boolean DBG = true;
private static final boolean VDBG = false;
@@ -177,52 +177,52 @@
* @hide
*/
public static final String EXTRA_PROTOCOL_MODE =
- "android.bluetooth.BluetoothInputDevice.extra.PROTOCOL_MODE";
+ "android.bluetooth.BluetoothHidHost.extra.PROTOCOL_MODE";
/**
* @hide
*/
public static final String EXTRA_REPORT_TYPE =
- "android.bluetooth.BluetoothInputDevice.extra.REPORT_TYPE";
+ "android.bluetooth.BluetoothHidHost.extra.REPORT_TYPE";
/**
* @hide
*/
public static final String EXTRA_REPORT_ID =
- "android.bluetooth.BluetoothInputDevice.extra.REPORT_ID";
+ "android.bluetooth.BluetoothHidHost.extra.REPORT_ID";
/**
* @hide
*/
public static final String EXTRA_REPORT_BUFFER_SIZE =
- "android.bluetooth.BluetoothInputDevice.extra.REPORT_BUFFER_SIZE";
+ "android.bluetooth.BluetoothHidHost.extra.REPORT_BUFFER_SIZE";
/**
* @hide
*/
- public static final String EXTRA_REPORT = "android.bluetooth.BluetoothInputDevice.extra.REPORT";
+ public static final String EXTRA_REPORT = "android.bluetooth.BluetoothHidHost.extra.REPORT";
/**
* @hide
*/
- public static final String EXTRA_STATUS = "android.bluetooth.BluetoothInputDevice.extra.STATUS";
+ public static final String EXTRA_STATUS = "android.bluetooth.BluetoothHidHost.extra.STATUS";
/**
* @hide
*/
public static final String EXTRA_VIRTUAL_UNPLUG_STATUS =
- "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS";
+ "android.bluetooth.BluetoothHidHost.extra.VIRTUAL_UNPLUG_STATUS";
/**
* @hide
*/
public static final String EXTRA_IDLE_TIME =
- "android.bluetooth.BluetoothInputDevice.extra.IDLE_TIME";
+ "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME";
private Context mContext;
private ServiceListener mServiceListener;
private BluetoothAdapter mAdapter;
- private volatile IBluetoothInputDevice mService;
+ private volatile IBluetoothHidHost mService;
private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
new IBluetoothStateChangeCallback.Stub() {
@@ -254,10 +254,10 @@
};
/**
- * Create a BluetoothInputDevice proxy object for interacting with the local
+ * Create a BluetoothHidHost proxy object for interacting with the local
* Bluetooth Service which handles the InputDevice profile
*/
- /*package*/ BluetoothInputDevice(Context context, ServiceListener l) {
+ /*package*/ BluetoothHidHost(Context context, ServiceListener l) {
mContext = context;
mServiceListener = l;
mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -275,7 +275,7 @@
}
boolean doBind() {
- Intent intent = new Intent(IBluetoothInputDevice.class.getName());
+ Intent intent = new Intent(IBluetoothHidHost.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
@@ -331,7 +331,7 @@
*/
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.connect(device);
@@ -371,7 +371,7 @@
*/
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.disconnect(device);
@@ -390,7 +390,7 @@
@Override
public List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled()) {
try {
return service.getConnectedDevices();
@@ -409,7 +409,7 @@
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled()) {
try {
return service.getDevicesMatchingConnectionStates(states);
@@ -428,7 +428,7 @@
@Override
public int getConnectionState(BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.getConnectionState(device);
@@ -458,7 +458,7 @@
*/
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
if (priority != BluetoothProfile.PRIORITY_OFF
&& priority != BluetoothProfile.PRIORITY_ON) {
@@ -490,7 +490,7 @@
*/
public int getPriority(BluetoothDevice device) {
if (VDBG) log("getPriority(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.getPriority(device);
@@ -506,11 +506,11 @@
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected");
- mService = IBluetoothInputDevice.Stub.asInterface(Binder.allowBlocking(service));
+ mService = IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service));
if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE,
- BluetoothInputDevice.this);
+ mServiceListener.onServiceConnected(BluetoothProfile.HID_HOST,
+ BluetoothHidHost.this);
}
}
@@ -518,7 +518,7 @@
if (DBG) Log.d(TAG, "Proxy object disconnected");
mService = null;
if (mServiceListener != null) {
- mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE);
+ mServiceListener.onServiceDisconnected(BluetoothProfile.HID_HOST);
}
}
};
@@ -542,7 +542,7 @@
*/
public boolean virtualUnplug(BluetoothDevice device) {
if (DBG) log("virtualUnplug(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.virtualUnplug(device);
@@ -568,7 +568,7 @@
*/
public boolean getProtocolMode(BluetoothDevice device) {
if (VDBG) log("getProtocolMode(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.getProtocolMode(device);
@@ -592,7 +592,7 @@
*/
public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
if (DBG) log("setProtocolMode(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.setProtocolMode(device, protocolMode);
@@ -623,7 +623,7 @@
log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId
+ "bufferSize=" + bufferSize);
}
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.getReport(device, reportType, reportId, bufferSize);
@@ -649,7 +649,7 @@
*/
public boolean setReport(BluetoothDevice device, byte reportType, String report) {
if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.setReport(device, reportType, report);
@@ -674,7 +674,7 @@
*/
public boolean sendData(BluetoothDevice device, String report) {
if (DBG) log("sendData(" + device + "), report=" + report);
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.sendData(device, report);
@@ -698,7 +698,7 @@
*/
public boolean getIdleTime(BluetoothDevice device) {
if (DBG) log("getIdletime(" + device + ")");
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.getIdleTime(device);
@@ -723,7 +723,7 @@
*/
public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
- final IBluetoothInputDevice service = mService;
+ final IBluetoothHidHost service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.setIdleTime(device, idleTime);
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index bc8fa84..46a230b 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -73,11 +73,11 @@
public static final int HEALTH = 3;
/**
- * Input Device Profile
+ * HID Host
*
* @hide
*/
- public static final int INPUT_DEVICE = 4;
+ public static final int HID_HOST = 4;
/**
* PAN Profile
@@ -152,11 +152,11 @@
public static final int MAP_CLIENT = 18;
/**
- * Input Host
+ * HID Device
*
* @hide
*/
- public static final int INPUT_HOST = 19;
+ public static final int HID_DEVICE = 19;
/**
* Max profile ID. This value should be updated whenever a new profile is added to match
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e47de75..dd729a3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3444,11 +3444,12 @@
/**
* A broadcast action to trigger a factory reset.
*
- * <p> The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission.
+ * <p>The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission. The
+ * reason for the factory reset should be specified as {@link #EXTRA_REASON}.
*
* <p>Not for use by third-party applications.
*
- * @see #EXTRA_FORCE_MASTER_CLEAR
+ * @see #EXTRA_FORCE_FACTORY_RESET
*
* {@hide}
*/
@@ -4827,7 +4828,13 @@
/** @hide */
public static final int EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT = 2;
- /** {@hide} */
+ /**
+ * Intent extra: the reason that the operation associated with this intent is being performed.
+ *
+ * <p>Type: String
+ * @hide
+ */
+ @SystemApi
public static final String EXTRA_REASON = "android.intent.extra.REASON";
/**
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 41667c4..837c00a 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -455,7 +455,6 @@
*/
public static final int FLAG_TURN_SCREEN_ON = 0x1000000;
-
/**
* @hide Bit in {@link #flags}: If set, this component will only be seen
* by the system user. Only works with broadcast receivers. Set from the
@@ -1001,20 +1000,12 @@
* Returns true if the activity's orientation is fixed.
* @hide
*/
- public boolean isFixedOrientation() {
+ boolean isFixedOrientation() {
return isFixedOrientationLandscape() || isFixedOrientationPortrait()
|| screenOrientation == SCREEN_ORIENTATION_LOCKED;
}
/**
- * Returns true if the specified orientation is considered fixed.
- * @hide
- */
- static public boolean isFixedOrientation(int orientation) {
- return isFixedOrientationLandscape(orientation) || isFixedOrientationPortrait(orientation);
- }
-
- /**
* Returns true if the activity's orientation is fixed to landscape.
* @hide
*/
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index d73f852..2034280 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -375,7 +375,7 @@
* {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext
* traffic. Third-party libraries are encouraged to honor this flag as well.
*
- * <p>NOTE: {@code WebView} does not honor this flag.
+ * <p>NOTE: {@code WebView} honors this flag for applications targeting API level 26 and up.
*
* <p>This flag is ignored on Android N and above if an Android Network Security Config is
* present.
@@ -1463,47 +1463,6 @@
}
}
- /**
- * @hide
- */
- public boolean isForwardLocked() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
- }
-
- /**
- * @hide
- */
- @TestApi
- public boolean isSystemApp() {
- return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- }
-
- /**
- * @hide
- */
- @TestApi
- public boolean isPrivilegedApp() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
- }
-
- /**
- * @hide
- */
- public boolean isUpdatedSystemApp() {
- return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
- }
-
- /** @hide */
- public boolean isInternal() {
- return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
- }
-
- /** @hide */
- public boolean isExternalAsec() {
- return TextUtils.isEmpty(volumeUuid)
- && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
- }
-
/** @hide */
public boolean isDefaultToDeviceProtectedStorage() {
return (privateFlags
@@ -1516,45 +1475,72 @@
}
/** @hide */
+ public boolean isEncryptionAware() {
+ return isDirectBootAware() || isPartiallyDirectBootAware();
+ }
+
+ /** @hide */
+ public boolean isExternal() {
+ return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
+ }
+
+ /** @hide */
+ public boolean isExternalAsec() {
+ return TextUtils.isEmpty(volumeUuid) && isExternal();
+ }
+
+ /** @hide */
+ public boolean isForwardLocked() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
+ }
+
+ /** @hide */
+ public boolean isInstantApp() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+ }
+
+ /** @hide */
+ public boolean isInternal() {
+ return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
+ }
+
+ /** @hide */
+ public boolean isOem() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+ }
+
+ /** @hide */
public boolean isPartiallyDirectBootAware() {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0;
}
/** @hide */
- public boolean isEncryptionAware() {
- return isDirectBootAware() || isPartiallyDirectBootAware();
+ @TestApi
+ public boolean isPrivilegedApp() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
- /**
- * @hide
- */
- public boolean isInstantApp() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
- }
-
- /**
- * @hide
- */
+ /** @hide */
public boolean isRequiredForSystemUser() {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0;
}
- /**
- * Returns true if the app has declared in its manifest that it wants its split APKs to be
- * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
- * @hide
- */
- public boolean requestsIsolatedSplitLoading() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
- }
-
- /**
- * @hide
- */
+ /** @hide */
public boolean isStaticSharedLibrary() {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY) != 0;
}
+ /** @hide */
+ @TestApi
+ public boolean isSystemApp() {
+ return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ /** @hide */
+ public boolean isUpdatedSystemApp() {
+ return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ }
+
/**
* Returns whether or not this application was installed as a virtual preload.
*/
@@ -1563,10 +1549,12 @@
}
/**
+ * Returns true if the app has declared in its manifest that it wants its split APKs to be
+ * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
* @hide
*/
- public boolean isOem() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+ public boolean requestsIsolatedSplitLoading() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
}
/**
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 143c51d..14cf855 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -337,6 +337,12 @@
public abstract boolean isPackagePersistent(String packageName);
/**
+ * Returns whether or not the given package represents a legacy system application released
+ * prior to runtime permissions.
+ */
+ public abstract boolean isLegacySystemApp(PackageParser.Package pkg);
+
+ /**
* Get all overlay packages for a user.
* @param userId The user for which to get the overlays.
* @return A list of overlay packages. An empty list is returned if the
@@ -467,7 +473,4 @@
/** Updates the flags for the given permission. */
public abstract void updatePermissionFlagsTEMP(@NonNull String permName,
@NonNull String packageName, int flagMask, int flagValues, int userId);
- /** Returns a PermissionGroup. */
- public abstract @Nullable PackageParser.PermissionGroup getPermissionGroupTEMP(
- @NonNull String groupName);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ad36139a..1c5cf15 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -102,6 +102,7 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -1708,13 +1709,33 @@
*/
public static ApkLite parseApkLite(File apkFile, int flags)
throws PackageParserException {
- final String apkPath = apkFile.getAbsolutePath();
+ return parseApkLiteInner(apkFile, null, null, flags);
+ }
+
+ /**
+ * Utility method that retrieves lightweight details about a single APK
+ * file, including package name, split name, and install location.
+ *
+ * @param fd already open file descriptor of an apk file
+ * @param debugPathName arbitrary text name for this file, for debug output
+ * @param flags optional parse flags, such as
+ * {@link #PARSE_COLLECT_CERTIFICATES}
+ */
+ public static ApkLite parseApkLite(FileDescriptor fd, String debugPathName, int flags)
+ throws PackageParserException {
+ return parseApkLiteInner(null, fd, debugPathName, flags);
+ }
+
+ private static ApkLite parseApkLiteInner(File apkFile, FileDescriptor fd, String debugPathName,
+ int flags) throws PackageParserException {
+ final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
AssetManager assets = null;
XmlResourceParser parser = null;
try {
assets = newConfiguredAssetManager();
- int cookie = assets.addAssetPath(apkPath);
+ int cookie = fd != null
+ ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath);
if (cookie == 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"Failed to parse " + apkPath);
@@ -6221,48 +6242,48 @@
return false;
}
- /**
- * @hide
- */
+ /** @hide */
+ public boolean isExternal() {
+ return applicationInfo.isExternal();
+ }
+
+ /** @hide */
public boolean isForwardLocked() {
return applicationInfo.isForwardLocked();
}
- /**
- * @hide
- */
- public boolean isSystemApp() {
- return applicationInfo.isSystemApp();
+ /** @hide */
+ public boolean isOem() {
+ return applicationInfo.isOem();
}
- /**
- * @hide
- */
- public boolean isPrivilegedApp() {
+ /** @hide */
+ public boolean isPrivileged() {
return applicationInfo.isPrivilegedApp();
}
- /**
- * @hide
- */
+ /** @hide */
+ public boolean isSystem() {
+ return applicationInfo.isSystemApp();
+ }
+
+ /** @hide */
public boolean isUpdatedSystemApp() {
return applicationInfo.isUpdatedSystemApp();
}
- /**
- * @hide
- */
+ /** @hide */
public boolean canHaveOatDir() {
// The following app types CANNOT have oat directory
// - non-updated system apps
// - forward-locked apps or apps installed in ASEC containers
- return (!isSystemApp() || isUpdatedSystemApp())
+ return (!isSystem() || isUpdatedSystemApp())
&& !isForwardLocked() && !applicationInfo.isExternalAsec();
}
public boolean isMatch(int flags) {
if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
- return isSystemApp();
+ return isSystem();
}
return true;
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index f0adcd6..7866560 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -28,8 +28,7 @@
import android.util.SparseArray;
import android.util.TypedValue;
-import dalvik.annotation.optimization.FastNative;
-
+import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -694,7 +693,35 @@
private native final int addAssetPathNative(String path, boolean appAsLib);
- /**
+ /**
+ * Add an additional set of assets to the asset manager from an already open
+ * FileDescriptor. Not for use by applications.
+ * This does not give full AssetManager functionality for these assets,
+ * since the origin of the file is not known for purposes of sharing,
+ * overlay resolution, and other features. However it does allow you
+ * to do simple access to the contents of the given fd as an apk file.
+ * Performs a dup of the underlying fd, so you must take care of still closing
+ * the FileDescriptor yourself (and can do that whenever you want).
+ * Returns the cookie of the added asset, or 0 on failure.
+ * {@hide}
+ */
+ public int addAssetFd(FileDescriptor fd, String debugPathName) {
+ return addAssetFdInternal(fd, debugPathName, false);
+ }
+
+ private int addAssetFdInternal(FileDescriptor fd, String debugPathName,
+ boolean appAsLib) {
+ synchronized (this) {
+ int res = addAssetFdNative(fd, debugPathName, appAsLib);
+ makeStringBlocks(mStringBlocks);
+ return res;
+ }
+ }
+
+ private native int addAssetFdNative(FileDescriptor fd, String debugPathName,
+ boolean appAsLib);
+
+ /**
* Add a set of assets to overlay an already added set of assets.
*
* This is only intended for application resources. System wide resources
diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java
index 28e9fce..6a4aae6 100644
--- a/core/java/android/content/res/FontResourcesParser.java
+++ b/core/java/android/content/res/FontResourcesParser.java
@@ -80,13 +80,15 @@
private int mWeight;
private int mItalic;
private int mTtcIndex;
+ private String mVariationSettings;
private int mResourceId;
public FontFileResourceEntry(@NonNull String fileName, int weight, int italic,
- int ttcIndex) {
+ @Nullable String variationSettings, int ttcIndex) {
mFileName = fileName;
mWeight = weight;
mItalic = italic;
+ mVariationSettings = variationSettings;
mTtcIndex = ttcIndex;
}
@@ -102,6 +104,10 @@
return mItalic;
}
+ public @Nullable String getVariationSettings() {
+ return mVariationSettings;
+ }
+
public int getTtcIndex() {
return mTtcIndex;
}
@@ -211,6 +217,8 @@
Typeface.RESOLVE_BY_FONT_TABLE);
int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle,
Typeface.RESOLVE_BY_FONT_TABLE);
+ String variationSettings = array.getString(
+ R.styleable.FontFamilyFont_fontVariationSettings);
int ttcIndex = array.getInt(R.styleable.FontFamilyFont_ttcIndex, 0);
String filename = array.getString(R.styleable.FontFamilyFont_font);
array.recycle();
@@ -220,7 +228,7 @@
if (filename == null) {
return null;
}
- return new FontFileResourceEntry(filename, weight, italic, ttcIndex);
+ return new FontFileResourceEntry(filename, weight, italic, variationSettings, ttcIndex);
}
private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 386239c..3239212 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -49,6 +49,8 @@
import android.util.Xml;
import android.view.DisplayAdjustments;
+import com.android.internal.util.GrowingArrayUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -117,6 +119,13 @@
private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
new ConfigurationBoundResourceCache<>();
+ // A stack of all the resourceIds already referenced when parsing a resource. This is used to
+ // detect circular references in the xml.
+ // Using a ThreadLocal variable ensures that we have different stacks for multiple parallel
+ // calls to ResourcesImpl
+ private final ThreadLocal<LookupStack> mLookupStack =
+ ThreadLocal.withInitial(() -> new LookupStack());
+
/** Size of the cyclical cache used to map XML files to blocks. */
private static final int XML_BLOCK_CACHE_SIZE = 4;
@@ -784,19 +793,29 @@
final Drawable dr;
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ LookupStack stack = mLookupStack.get();
try {
- if (file.endsWith(".xml")) {
- final XmlResourceParser rp = loadXmlResourceParser(
- file, id, value.assetCookie, "drawable");
- dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
- rp.close();
- } else {
- final InputStream is = mAssets.openNonAsset(
- value.assetCookie, file, AssetManager.ACCESS_STREAMING);
- dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
- is.close();
+ // Perform a linear search to check if we have already referenced this resource before.
+ if (stack.contains(id)) {
+ throw new Exception("Recursive reference in drawable");
}
- } catch (Exception | StackOverflowError e) {
+ stack.push(id);
+ try {
+ if (file.endsWith(".xml")) {
+ final XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "drawable");
+ dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
+ rp.close();
+ } else {
+ final InputStream is = mAssets.openNonAsset(
+ value.assetCookie, file, AssetManager.ACCESS_STREAMING);
+ dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
+ is.close();
+ }
+ } finally {
+ stack.pop();
+ }
+ } catch (Exception e) {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
final NotFoundException rnf = new NotFoundException(
"File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
@@ -1377,4 +1396,29 @@
}
}
}
+
+ private static class LookupStack {
+
+ // Pick a reasonable default size for the array, it is grown as needed.
+ private int[] mIds = new int[4];
+ private int mSize = 0;
+
+ public void push(int id) {
+ mIds = GrowingArrayUtils.append(mIds, mSize, id);
+ mSize++;
+ }
+
+ public boolean contains(int id) {
+ for (int i = 0; i < mSize; i++) {
+ if (mIds[i] == id) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void pop() {
+ mSize--;
+ }
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index c28583e..361b81b 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -289,7 +289,9 @@
private void setWalModeFromConfiguration() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
- if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+ boolean walEnabled =
+ (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+ if (walEnabled || mConfiguration.useCompatibilityWal) {
setJournalMode("WAL");
setSyncMode(SQLiteGlobal.getWALSyncMode());
} else {
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 2dc5ca4..13e6f71 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -22,6 +22,8 @@
import android.os.StrictMode;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.util.HashMap;
import java.util.Map;
@@ -60,6 +62,9 @@
/** Used to find out where a cursor was allocated in case it never got released. */
private final Throwable mStackTrace;
+ /** Controls fetching of rows relative to requested position **/
+ private boolean mFillWindowForwardOnly;
+
/**
* Execute a query and provide access to its result set through a Cursor
* interface. For a query such as: {@code SELECT name, birth, phone FROM
@@ -136,18 +141,19 @@
private void fillWindow(int requiredPos) {
clearOrCreateWindow(getDatabase().getPath());
-
try {
+ Preconditions.checkArgumentNonnegative(requiredPos,
+ "requiredPos cannot be negative, but was " + requiredPos);
+
if (mCount == NO_COUNT) {
- int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
- mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
+ mCount = mQuery.fillWindow(mWindow, requiredPos, requiredPos, true);
mCursorWindowCapacity = mWindow.getNumRows();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
}
} else {
- int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
- mCursorWindowCapacity);
+ int startPos = mFillWindowForwardOnly ? requiredPos : DatabaseUtils
+ .cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity);
mQuery.fillWindow(mWindow, startPos, requiredPos, false);
}
} catch (RuntimeException ex) {
@@ -252,6 +258,20 @@
}
/**
+ * Controls fetching of rows relative to requested position.
+ *
+ * <p>Calling this method defines how rows will be loaded, but it doesn't affect rows that
+ * are already in the window. This setting is preserved if a new window is
+ * {@link #setWindow(CursorWindow) set}
+ *
+ * @param fillWindowForwardOnly if true, rows will be fetched starting from requested position
+ * up to the window's capacity. Default value is false.
+ */
+ public void setFillWindowForwardOnly(boolean fillWindowForwardOnly) {
+ mFillWindowForwardOnly = fillWindowForwardOnly;
+ }
+
+ /**
* Release the native resources, if they haven't been released yet.
*/
@Override
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index df0e262..83b8dc7 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -285,6 +285,7 @@
}
}
mConfigurationLocked.idleConnectionTimeoutMs = effectiveTimeoutMs;
+ mConfigurationLocked.useCompatibilityWal = SQLiteGlobal.isCompatibilityWalSupported();
}
@Override
@@ -2070,15 +2071,21 @@
synchronized (mLock) {
throwIfNotOpenLocked();
- if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) {
+ final boolean oldUseCompatibilityWal = mConfigurationLocked.useCompatibilityWal;
+ final int oldFlags = mConfigurationLocked.openFlags;
+ if (!oldUseCompatibilityWal && (oldFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) {
return;
}
mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING;
+ // If an app explicitly disables WAL, do not even use compatibility mode
+ mConfigurationLocked.useCompatibilityWal = false;
+
try {
mConnectionPoolLocked.reconfigure(mConfigurationLocked);
} catch (RuntimeException ex) {
- mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING;
+ mConfigurationLocked.openFlags = oldFlags;
+ mConfigurationLocked.useCompatibilityWal = oldUseCompatibilityWal;
throw ex;
}
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 34c9b33..905da724 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -111,6 +111,15 @@
public long idleConnectionTimeoutMs = Long.MAX_VALUE;
/**
+ * Enables compatibility WAL mode. Applications cannot explicitly choose compatibility WAL mode,
+ * therefore it is not exposed as a flag.
+ *
+ * <p>In this mode, only database journal mode will be changed, connection pool
+ * size will still be limited to a single connection.
+ */
+ public boolean useCompatibilityWal;
+
+ /**
* Creates a database configuration with the required parameters for opening a
* database and default values for all other parameters.
*
@@ -170,6 +179,7 @@
lookasideSlotSize = other.lookasideSlotSize;
lookasideSlotCount = other.lookasideSlotCount;
idleConnectionTimeoutMs = other.idleConnectionTimeoutMs;
+ useCompatibilityWal = other.useCompatibilityWal;
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
index 94d5555..bb2a517 100644
--- a/core/java/android/database/sqlite/SQLiteGlobal.java
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -81,6 +81,17 @@
}
/**
+ * Returns true if compatibility WAL mode is supported. In this mode, only
+ * database journal mode is changed. Connection pool will use at most one connection.
+ * @hide
+ */
+ public static boolean isCompatibilityWalSupported() {
+ return SystemProperties.getBoolean("debug.sqlite.compatibility_wal_supported",
+ Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.db_compatibility_wal_supported));
+ }
+
+ /**
* Gets the journal size limit in bytes.
*/
public static int getJournalSizeLimit() {
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
new file mode 100644
index 0000000..b7e353a
--- /dev/null
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.location;
+
+import android.annotation.RequiresPermission;
+import android.os.Handler;
+
+import java.io.Closeable;
+
+/**
+ * A class describing a client of the Context Hub Service.
+ *
+ * Clients can send messages to nanoapps at a Context Hub through this object.
+ *
+ * @hide
+ */
+public class ContextHubClient implements Closeable {
+ /*
+ * The ContextHubClient interface associated with this client.
+ */
+ // TODO: Implement this interface and associate with ContextHubClient object
+ // private final IContextHubClient mClientInterface;
+
+ /*
+ * The listening callback associated with this client.
+ */
+ private ContextHubClientCallback mCallback;
+
+ /*
+ * The Context Hub that this client is attached to.
+ */
+ private ContextHubInfo mAttachedHub;
+
+ /*
+ * The handler to invoke mCallback.
+ */
+ private Handler mCallbackHandler;
+
+ ContextHubClient(ContextHubClientCallback callback, Handler handler, ContextHubInfo hubInfo) {
+ mCallback = callback;
+ mCallbackHandler = handler;
+ mAttachedHub = hubInfo;
+ }
+
+ /**
+ * Returns the hub that this client is attached to.
+ *
+ * @return the ContextHubInfo of the attached hub
+ */
+ public ContextHubInfo getAttachedHub() {
+ return mAttachedHub;
+ }
+
+ /**
+ * Closes the connection for this client and the Context Hub Service.
+ *
+ * When this function is invoked, the messaging associated with this client is invalidated.
+ * All futures messages targeted for this client are dropped at the service.
+ */
+ public void close() {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Sends a message to a nanoapp through the Context Hub Service.
+ *
+ * This function returns TRANSACTION_SUCCESS if the message has reached the HAL, but
+ * does not guarantee delivery of the message to the target nanoapp.
+ *
+ * @param message the message object to send
+ *
+ * @return the result of sending the message defined as in ContextHubTransaction.Result
+ *
+ * @see NanoAppMessage
+ * @see ContextHubTransaction.Result
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ @ContextHubTransaction.Result
+ public int sendMessageToNanoApp(NanoAppMessage message) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+}
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
new file mode 100644
index 0000000..ab19d54
--- /dev/null
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.location;
+
+/**
+ * A class for {@link android.hardware.location.ContextHubClient ContextHubClient} to
+ * receive messages and life-cycle events from nanoapps in the Context Hub at which the client is
+ * attached to.
+ *
+ * This callback is registered through the
+ * {@link android.hardware.location.ContextHubManager#createClient() creation} of
+ * {@link android.hardware.location.ContextHubClient ContextHubClient}. Callbacks are
+ * invoked in the following ways:
+ * 1) Messages from nanoapps delivered through onMessageFromNanoApp may either be broadcasted
+ * or targeted to a specific client.
+ * 2) Nanoapp or Context Hub events (the remaining callbacks) are broadcasted to all clients, and
+ * the client can choose to ignore the event by filtering through the parameters.
+ *
+ * @hide
+ */
+public class ContextHubClientCallback {
+ /**
+ * Callback invoked when receiving a message from a nanoapp.
+ *
+ * The message contents of this callback may either be broadcasted or targeted to the
+ * client receiving the invocation.
+ *
+ * @param message the message sent by the nanoapp
+ */
+ public void onMessageFromNanoApp(NanoAppMessage message) {}
+
+ /**
+ * Callback invoked when the attached Context Hub has reset.
+ */
+ public void onHubReset() {}
+
+ /**
+ * Callback invoked when a nanoapp aborts at the attached Context Hub.
+ *
+ * @param nanoAppId the ID of the nanoapp that had aborted
+ * @param abortCode the reason for nanoapp's abort, specific to each nanoapp
+ */
+ public void onNanoAppAborted(long nanoAppId, int abortCode) {}
+
+ /**
+ * Callback invoked when a nanoapp is loaded at the attached Context Hub.
+ *
+ * @param nanoAppId the ID of the nanoapp that had been loaded
+ */
+ public void onNanoAppLoaded(long nanoAppId) {}
+
+ /**
+ * Callback invoked when a nanoapp is unloaded from the attached Context Hub.
+ *
+ * @param nanoAppId the ID of the nanoapp that had been unloaded
+ */
+ public void onNanoAppUnloaded(long nanoAppId) {}
+
+ /**
+ * Callback invoked when a nanoapp is enabled at the attached Context Hub.
+ *
+ * @param nanoAppId the ID of the nanoapp that had been enabled
+ */
+ public void onNanoAppEnabled(long nanoAppId) {}
+
+ /**
+ * Callback invoked when a nanoapp is disabled at the attached Context Hub.
+ *
+ * @param nanoAppId the ID of the nanoapp that had been disabled
+ */
+ public void onNanoAppDisabled(long nanoAppId) {}
+}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 6050046..7cbb436 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -27,6 +28,8 @@
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.Log;
+import java.util.List;
+
/**
* A class that exposes the Context hubs on a device to applications.
*
@@ -38,7 +41,6 @@
@SystemApi
@SystemService(Context.CONTEXTHUB_SERVICE)
public final class ContextHubManager {
-
private static final String TAG = "ContextHubManager";
private final Looper mMainLooper;
@@ -256,6 +258,100 @@
}
/**
+ * Returns the list of context hubs in the system.
+ *
+ * @return the list of context hub informations
+ *
+ * @see ContextHubInfo
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public List<ContextHubInfo> getContextHubs() {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Loads a nanoapp at the specified Context Hub.
+ *
+ * After the nanoapp binary is successfully loaded at the specified hub, the nanoapp will be in
+ * the enabled state.
+ *
+ * @param hubInfo the hub to load the nanoapp on
+ * @param appBinary The app binary to load
+ *
+ * @return the ContextHubTransaction of the request
+ *
+ * @see NanoAppBinary
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public ContextHubTransaction<Void> loadNanoApp(
+ ContextHubInfo hubInfo, NanoAppBinary appBinary) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Unloads a nanoapp at the specified Context Hub.
+ *
+ * @param hubInfo the hub to unload the nanoapp from
+ * @param nanoAppId the app to unload
+ *
+ * @return the ContextHubTransaction of the request
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public ContextHubTransaction<Void> unloadNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Enables a nanoapp at the specified Context Hub.
+ *
+ * @param hubInfo the hub to enable the nanoapp on
+ * @param nanoAppId the app to enable
+ *
+ * @return the ContextHubTransaction of the request
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public ContextHubTransaction<Void> enableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Disables a nanoapp at the specified Context Hub.
+ *
+ * @param hubInfo the hub to disable the nanoapp on
+ * @param nanoAppId the app to disable
+ *
+ * @return the ContextHubTransaction of the request
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public ContextHubTransaction<Void> disableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Requests a query for nanoapps loaded at the specified Context Hub.
+ *
+ * @param hubInfo the hub to query a list of nanoapps from
+ *
+ * @return the ContextHubTransaction of the request
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public ContextHubTransaction<List<NanoAppState>> queryNanoApps(ContextHubInfo hubInfo) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
* Set a callback to receive messages from the context hub
*
* @param callback Callback object
@@ -307,6 +403,29 @@
}
/**
+ * Creates and registers a client and its callback with the Context Hub Service.
+ *
+ * A client is registered with the Context Hub Service for a specified Context Hub. When the
+ * registration succeeds, the client can send messages to nanoapps through the returned
+ * {@link ContextHubClient} object, and receive notifications through the provided callback.
+ *
+ * @param callback the notification callback to register
+ * @param hubInfo the hub to attach this client to
+ * @param handler the handler to invoke the callback, if null uses the current thread Looper
+ *
+ * @return the registered client object
+ *
+ * @see ContextHubClientCallback
+ *
+ * @hide
+ */
+ public ContextHubClient createClient(
+ ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) {
+ throw new UnsupportedOperationException(
+ "TODO: Implement this, and throw an exception on error");
+ }
+
+ /**
* Unregister a callback for receive messages from the context hub.
*
* @see Callback
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
new file mode 100644
index 0000000..4877d38
--- /dev/null
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.location;
+
+import android.annotation.IntDef;
+import android.os.Handler;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class describing a request sent to the Context Hub Service.
+ *
+ * This object is generated as a result of an asynchronous request sent to the Context Hub
+ * through the ContextHubManager APIs. The caller can either retrieve the result
+ * synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or
+ * asynchronously through a user-defined callback
+ * ({@link #onComplete(ContextHubTransaction.Callback<T>, Handler)}).
+ *
+ * A transaction can be invalidated if the caller of the transaction is no longer active
+ * and the reference to this object is lost, or if timeout period has passed in
+ * {@link #waitForResponse(long, TimeUnit)}.
+ *
+ * @param <T> the type of the contents in the transaction response
+ *
+ * @hide
+ */
+public class ContextHubTransaction<T> {
+ /**
+ * Constants describing the type of a transaction through the Context Hub Service.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ TYPE_LOAD_NANOAPP,
+ TYPE_UNLOAD_NANOAPP,
+ TYPE_ENABLE_NANOAPP,
+ TYPE_DISABLE_NANOAPP,
+ TYPE_QUERY_NANOAPPS})
+ public @interface Type {}
+ public static final int TYPE_LOAD_NANOAPP = 0;
+ public static final int TYPE_UNLOAD_NANOAPP = 1;
+ public static final int TYPE_ENABLE_NANOAPP = 2;
+ public static final int TYPE_DISABLE_NANOAPP = 3;
+ public static final int TYPE_QUERY_NANOAPPS = 4;
+
+ /**
+ * Constants describing the result of a transaction or request through the Context Hub Service.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ TRANSACTION_SUCCESS,
+ TRANSACTION_FAILED_UNKNOWN,
+ TRANSACTION_FAILED_BAD_PARAMS,
+ TRANSACTION_FAILED_UNINITIALIZED,
+ TRANSACTION_FAILED_PENDING,
+ TRANSACTION_FAILED_AT_HUB,
+ TRANSACTION_FAILED_TIMEOUT})
+ public @interface Result {}
+ public static final int TRANSACTION_SUCCESS = 0;
+ /**
+ * Generic failure mode.
+ */
+ public static final int TRANSACTION_FAILED_UNKNOWN = 1;
+ /**
+ * Failure mode when the request parameters were not valid.
+ */
+ public static final int TRANSACTION_FAILED_BAD_PARAMS = 2;
+ /**
+ * Failure mode when the Context Hub is not initialized.
+ */
+ public static final int TRANSACTION_FAILED_UNINITIALIZED = 3;
+ /**
+ * Failure mode when there are too many transactions pending.
+ */
+ public static final int TRANSACTION_FAILED_PENDING = 4;
+ /**
+ * Failure mode when the request went through, but failed asynchronously at the hub.
+ */
+ public static final int TRANSACTION_FAILED_AT_HUB = 5;
+ /**
+ * Failure mode when the transaction has timed out.
+ */
+ public static final int TRANSACTION_FAILED_TIMEOUT = 6;
+
+ /**
+ * A class describing the response for a ContextHubTransaction.
+ *
+ * @param <R> the type of the contents in the response
+ */
+ public static class Response<R> {
+ /*
+ * The result of the transaction.
+ */
+ @ContextHubTransaction.Result
+ private int mResult;
+
+ /*
+ * The contents of the response from the Context Hub.
+ */
+ private R mContents;
+
+ Response(@ContextHubTransaction.Result int result, R contents) {
+ mResult = result;
+ mContents = contents;
+ }
+
+ @ContextHubTransaction.Result
+ public int getResult() {
+ return mResult;
+ }
+
+ public R getContents() {
+ return mContents;
+ }
+ }
+
+ /**
+ * An interface describing the callback to be invoked when a transaction completes.
+ *
+ * @param <C> the type of the contents in the transaction response
+ */
+ @FunctionalInterface
+ public interface Callback<C> {
+ /**
+ * The callback to invoke when the transaction completes.
+ *
+ * @param transaction the transaction that this callback was attached to.
+ * @param response the response of the transaction.
+ */
+ void onComplete(
+ ContextHubTransaction<C> transaction, ContextHubTransaction.Response<C> response);
+ }
+
+ /*
+ * The unique identifier representing the transaction.
+ */
+ private int mTransactionId;
+
+ /*
+ * The type of the transaction.
+ */
+ @Type
+ private int mTransactionType;
+
+ /*
+ * The response of the transaction.
+ */
+ private ContextHubTransaction.Response<T> mResponse;
+
+ /*
+ * The handler to invoke the aynsc response supplied by onComplete.
+ */
+ private Handler mHandler = null;
+
+ /*
+ * The callback to invoke when the transaction completes.
+ */
+ private ContextHubTransaction.Callback<T> mCallback = null;
+
+ ContextHubTransaction(int id, @Type int type) {
+ mTransactionId = id;
+ mTransactionType = type;
+ }
+
+ /**
+ * @return the type of the transaction
+ */
+ @Type
+ public int getType() {
+ return mTransactionType;
+ }
+
+ /**
+ * Waits to receive the asynchronous transaction result.
+ *
+ * This function blocks until the Context Hub Service has received a response
+ * for the transaction represented by this object by the Context Hub, or a
+ * specified timeout period has elapsed.
+ *
+ * If the specified timeout has passed, the transaction represented by this object
+ * is invalidated by the Context Hub Service (resulting in a timeout failure in the
+ * response).
+ *
+ * @param timeout the timeout duration
+ * @param unit the unit of the timeout
+ *
+ * @return the transaction response
+ */
+ public ContextHubTransaction.Response<T> waitForResponse(long timeout, TimeUnit unit) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Sets a callback to be invoked when the transaction completes.
+ *
+ * This function provides an asynchronous approach to retrieve the result of the
+ * transaction. When the transaction response has been provided by the Context Hub,
+ * the given callback will be posted by the provided handler.
+ *
+ * If the transaction has already completed at the time of invocation, the callback
+ * will be immediately posted by the handler. If the transaction has been invalidated,
+ * the callback will never be invoked.
+ *
+ * @param callback the callback to be invoked upon completion
+ * @param handler the handler to post the callback
+ */
+ public void onComplete(ContextHubTransaction.Callback<T> callback, Handler handler) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ private void setResponse(ContextHubTransaction.Response<T> response) {
+ mResponse = response;
+ throw new UnsupportedOperationException("TODO: Unblock waitForResponse");
+ }
+}
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/java/android/hardware/location/NanoAppBinary.aidl
similarity index 71%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/java/android/hardware/location/NanoAppBinary.aidl
index a987a16..ff50c93 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/java/android/hardware/location/NanoAppBinary.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package android.hardware.location;
-import android.graphics.Bitmap;
-
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
-}
+/**
+ * @hide
+ */
+parcelable NanoAppBinary;
diff --git a/core/java/android/hardware/location/NanoAppBinary.java b/core/java/android/hardware/location/NanoAppBinary.java
new file mode 100644
index 0000000..5454227
--- /dev/null
+++ b/core/java/android/hardware/location/NanoAppBinary.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * @hide
+ */
+public final class NanoAppBinary implements Parcelable {
+ private static final String TAG = "NanoAppBinary";
+
+ /*
+ * The contents of the app binary.
+ */
+ private byte[] mNanoAppBinary;
+
+ /*
+ * Contents of the nanoapp binary header.
+ *
+ * Only valid if mHasValidHeader is true.
+ * See nano_app_binary_t in context_hub.h for details.
+ */
+ private int mHeaderVersion;
+ private int mMagic;
+ private long mNanoAppId;
+ private int mNanoAppVersion;
+ private int mFlags;
+ private long mHwHubType;
+ private byte mTargetChreApiMajorVersion;
+ private byte mTargetChreApiMinorVersion;
+
+ private boolean mHasValidHeader = false;
+
+ /*
+ * The header version used to parse the binary in parseBinaryHeader().
+ */
+ private static final int EXPECTED_HEADER_VERSION = 1;
+
+ /*
+ * The magic value expected in the header.
+ */
+ private static final int EXPECTED_MAGIC_VALUE =
+ (((int) 'N' << 0) | ((int) 'A' << 8) | ((int) 'N' << 16) | ((int) 'O' << 24));
+
+ /*
+ * Byte order established in context_hub.h
+ */
+ private static final ByteOrder HEADER_ORDER = ByteOrder.LITTLE_ENDIAN;
+
+ public NanoAppBinary(byte[] appBinary) {
+ mNanoAppBinary = appBinary;
+ parseBinaryHeader();
+ }
+
+ /*
+ * Parses the binary header and populates its field using mNanoAppBinary.
+ */
+ private void parseBinaryHeader() {
+ ByteBuffer buf = ByteBuffer.wrap(mNanoAppBinary).order(HEADER_ORDER);
+
+ mHasValidHeader = false;
+ try {
+ mHeaderVersion = buf.getInt();
+ if (mHeaderVersion != EXPECTED_HEADER_VERSION) {
+ Log.e(TAG, "Unexpected header version " + mHeaderVersion + " while parsing header"
+ + " (expected " + EXPECTED_HEADER_VERSION + ")");
+ return;
+ }
+
+ mMagic = buf.getInt();
+ mNanoAppId = buf.getLong();
+ mNanoAppVersion = buf.getInt();
+ mFlags = buf.getInt();
+ mHwHubType = buf.getLong();
+ mTargetChreApiMajorVersion = buf.get();
+ mTargetChreApiMinorVersion = buf.get();
+ } catch (BufferUnderflowException e) {
+ Log.e(TAG, "Not enough contents in nanoapp header");
+ return;
+ }
+
+ if (mMagic != EXPECTED_MAGIC_VALUE) {
+ Log.e(TAG, "Unexpected magic value " + String.format("0x%08X", mMagic)
+ + "while parsing header (expected "
+ + String.format("0x%08X", EXPECTED_MAGIC_VALUE) + ")");
+ } else {
+ mHasValidHeader = true;
+ }
+ }
+
+ /**
+ * @return the app binary byte array
+ */
+ public byte[] getNanoAppBinary() {
+ return mNanoAppBinary;
+ }
+
+ /**
+ * @return {@code true} if the header is valid, {@code false} otherwise
+ */
+ public boolean hasValidHeader() {
+ return mHasValidHeader;
+ }
+
+ /**
+ * @return the header version
+ */
+ public int getHeaderVersion() {
+ return mHeaderVersion;
+ }
+
+ /**
+ * @return the app ID parsed from the nanoapp header
+ */
+ public long getNanoAppId() {
+ return mNanoAppId;
+ }
+
+ /**
+ * @return the app version parsed from the nanoapp header
+ */
+ public int getNanoAppVersion() {
+ return mNanoAppVersion;
+ }
+
+ /**
+ * @return the compile target hub type parsed from the nanoapp header
+ */
+ public long getHwHubType() {
+ return mHwHubType;
+ }
+
+ /**
+ * @return the target CHRE API major version parsed from the nanoapp header
+ */
+ public byte getTargetChreApiMajorVersion() {
+ return mTargetChreApiMajorVersion;
+ }
+
+ /**
+ * @return the target CHRE API minor version parsed from the nanoapp header
+ */
+ public byte getTargetChreApiMinorVersion() {
+ return mTargetChreApiMinorVersion;
+ }
+
+ private NanoAppBinary(Parcel in) {
+ int binaryLength = in.readInt();
+ mNanoAppBinary = new byte[binaryLength];
+ in.readByteArray(mNanoAppBinary);
+
+ parseBinaryHeader();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mNanoAppBinary.length);
+ out.writeByteArray(mNanoAppBinary);
+ }
+
+ public static final Creator<NanoAppBinary> CREATOR =
+ new Creator<NanoAppBinary>() {
+ @Override
+ public NanoAppBinary createFromParcel(Parcel in) {
+ return new NanoAppBinary(in);
+ }
+
+ @Override
+ public NanoAppBinary[] newArray(int size) {
+ return new NanoAppBinary[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/java/android/hardware/location/NanoAppMessage.aidl
similarity index 71%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/java/android/hardware/location/NanoAppMessage.aidl
index a987a16..f1f4ff6 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/java/android/hardware/location/NanoAppMessage.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package android.hardware.location;
-import android.graphics.Bitmap;
-
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
-}
+/**
+ * @hide
+ */
+parcelable NanoAppMessage;
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
new file mode 100644
index 0000000..2028674
--- /dev/null
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class describing messages send to or from nanoapps through the Context Hub Service.
+ *
+ * The basis of the class is in the IContextHub.hal ContextHubMsg definition.
+ *
+ * @hide
+ */
+public final class NanoAppMessage implements Parcelable {
+ private long mNanoAppId;
+ private int mMessageType;
+ private byte[] mMessageBody;
+ private boolean mIsBroadcasted;
+
+ private NanoAppMessage(
+ long nanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
+ mNanoAppId = nanoAppId;
+ mMessageType = messageType;
+ mMessageBody = messageBody;
+ mIsBroadcasted = broadcasted;
+ }
+
+ /**
+ * Creates a NanoAppMessage object to send to a nanoapp.
+ *
+ * This factory method can be used to generate a NanoAppMessage object to be used in
+ * the ContextHubClient.sendMessageToNanoApp API.
+ *
+ * @param targetNanoAppId the ID of the nanoapp to send the message to
+ * @param messageType the nanoapp-dependent message type
+ * @param messageBody the byte array message contents
+ *
+ * @return the NanoAppMessage object
+ */
+ public static NanoAppMessage createMessageToNanoApp(
+ long targetNanoAppId, int messageType, byte[] messageBody) {
+ return new NanoAppMessage(
+ targetNanoAppId, messageType, messageBody, false /* broadcasted */);
+ }
+
+ /**
+ * Creates a NanoAppMessage object sent from a nanoapp.
+ *
+ * This factory method is intended only to be used by the Context Hub Service when delivering
+ * messages from a nanoapp to clients.
+ *
+ * @param sourceNanoAppId the ID of the nanoapp that the message was sent from
+ * @param messageType the nanoapp-dependent message type
+ * @param messageBody the byte array message contents
+ * @param broadcasted {@code true} if the message was broadcasted, {@code false} otherwise
+ *
+ * @return the NanoAppMessage object
+ */
+ public static NanoAppMessage createMessageFromNanoApp(
+ long sourceNanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
+ return new NanoAppMessage(sourceNanoAppId, messageType, messageBody, broadcasted);
+ }
+
+ /**
+ * @return the ID of the source or destination nanoapp
+ */
+ public long getNanoAppId() {
+ return mNanoAppId;
+ }
+
+ /**
+ * @return the type of the message that is nanoapp-dependent
+ */
+ public int getMessageType() {
+ return mMessageType;
+ }
+
+ /**
+ * @return the byte array contents of the message
+ */
+ public byte[] getMessageBody() {
+ return mMessageBody;
+ }
+
+ /**
+ * @return {@code true} if the message is broadcasted, {@code false} otherwise
+ */
+ public boolean isBroadcastMessage() {
+ return mIsBroadcasted;
+ }
+
+ private NanoAppMessage(Parcel in) {
+ mNanoAppId = in.readLong();
+ mIsBroadcasted = (in.readInt() == 1);
+ mMessageType = in.readInt();
+
+ int msgSize = in.readInt();
+ mMessageBody = new byte[msgSize];
+ in.readByteArray(mMessageBody);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mNanoAppId);
+ out.writeInt(mIsBroadcasted ? 1 : 0);
+ out.writeInt(mMessageType);
+
+ out.writeInt(mMessageBody.length);
+ out.writeByteArray(mMessageBody);
+ }
+
+ public static final Creator<NanoAppMessage> CREATOR =
+ new Creator<NanoAppMessage>() {
+ @Override
+ public NanoAppMessage createFromParcel(Parcel in) {
+ return new NanoAppMessage(in);
+ }
+
+ @Override
+ public NanoAppMessage[] newArray(int size) {
+ return new NanoAppMessage[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/java/android/hardware/location/NanoAppState.aidl
similarity index 71%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/java/android/hardware/location/NanoAppState.aidl
index a987a16..f80f435 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/java/android/hardware/location/NanoAppState.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package android.hardware.location;
-import android.graphics.Bitmap;
-
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
-}
+/**
+ * @hide
+ */
+parcelable NanoAppState;
diff --git a/core/java/android/hardware/location/NanoAppState.java b/core/java/android/hardware/location/NanoAppState.java
new file mode 100644
index 0000000..644031b
--- /dev/null
+++ b/core/java/android/hardware/location/NanoAppState.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class describing the nanoapp state information resulting from a query to a Context Hub.
+ *
+ * @hide
+ */
+public final class NanoAppState implements Parcelable {
+ private long mNanoAppId;
+ private int mNanoAppVersion;
+ private boolean mIsEnabled;
+
+ public NanoAppState(long nanoAppId, int appVersion, boolean enabled) {
+ mNanoAppId = nanoAppId;
+ mNanoAppVersion = appVersion;
+ mIsEnabled = enabled;
+ }
+
+ /**
+ * @return the NanoAppInfo for this app
+ */
+ public long getNanoAppId() {
+ return mNanoAppId;
+ }
+
+ /**
+ * @return the app version
+ */
+ public long getNanoAppVersion() {
+ return mNanoAppVersion;
+ }
+
+ /**
+ * @return {@code true} if the app is enabled at the Context Hub, {@code false} otherwise
+ */
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ private NanoAppState(Parcel in) {
+ mNanoAppId = in.readLong();
+ mNanoAppVersion = in.readInt();
+ mIsEnabled = (in.readInt() == 1);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mNanoAppId);
+ out.writeInt(mNanoAppVersion);
+ out.writeInt(mIsEnabled ? 1 : 0);
+ }
+
+ public static final Creator<NanoAppState> CREATOR =
+ new Creator<NanoAppState>() {
+ @Override
+ public NanoAppState createFromParcel(Parcel in) {
+ return new NanoAppState(in);
+ }
+
+ @Override
+ public NanoAppState[] newArray(int size) {
+ return new NanoAppState[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 595d857..6ce9669 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -102,7 +102,7 @@
"android.hardware.usb.action.USB_PORT_CHANGED";
/**
- * Broadcast Action: A broadcast for USB device attached event.
+ * Activity intent sent when user attaches a USB device.
*
* This intent is sent when a USB device is attached to the USB bus when in host mode.
* <ul>
@@ -128,9 +128,8 @@
"android.hardware.usb.action.USB_DEVICE_DETACHED";
/**
- * Broadcast Action: A broadcast for USB accessory attached event.
+ * Activity intent sent when user attaches a USB accessory.
*
- * This intent is sent when a USB accessory is attached.
* <ul>
* <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory}
* for the attached accessory
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 2468225f..2c7e51a 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -48,6 +48,7 @@
import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Implements the internal IInputMethod interface to convert incoming calls
@@ -76,6 +77,20 @@
final WeakReference<InputMethod> mInputMethod;
final int mTargetSdkVersion;
+ /**
+ * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
+ * so that {@link InputConnectionWrapper} can query if {@link #unbindInput()} has already been
+ * called or not, mainly to avoid unnecessary blocking operations.
+ *
+ * <p>This field must be set and cleared only from the binder thread(s), where the system
+ * guarantees that {@link #bindInput(InputBinding)},
+ * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
+ * {@link #unbindInput()} are called with the same order as the original calls
+ * in {@link com.android.server.InputMethodManagerService}. See {@link IBinder#FLAG_ONEWAY}
+ * for detailed semantics.</p>
+ */
+ AtomicBoolean mIsUnbindIssued = null;
+
// NOTE: we should have a cache of these.
static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
final Context mContext;
@@ -163,8 +178,10 @@
final IBinder startInputToken = (IBinder) args.arg1;
final IInputContext inputContext = (IInputContext) args.arg2;
final EditorInfo info = (EditorInfo) args.arg3;
+ final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
final InputConnection ic = inputContext != null
- ? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null;
+ ? new InputConnectionWrapper(
+ mTarget, inputContext, missingMethods, isUnbindIssued) : null;
info.makeCompatible(mTargetSdkVersion);
inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */,
startInputToken);
@@ -236,10 +253,15 @@
@BinderThread
@Override
public void bindInput(InputBinding binding) {
+ if (mIsUnbindIssued != null) {
+ Log.e(TAG, "bindInput must be paired with unbindInput.");
+ }
+ mIsUnbindIssued = new AtomicBoolean();
// This IInputContext is guaranteed to implement all the methods.
final int missingMethodFlags = 0;
InputConnection ic = new InputConnectionWrapper(mTarget,
- IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags);
+ IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
+ mIsUnbindIssued);
InputBinding nu = new InputBinding(ic, binding);
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
}
@@ -247,6 +269,13 @@
@BinderThread
@Override
public void unbindInput() {
+ if (mIsUnbindIssued != null) {
+ // Signal the flag then forget it.
+ mIsUnbindIssued.set(true);
+ mIsUnbindIssued = null;
+ } else {
+ Log.e(TAG, "unbindInput must be paired with bindInput.");
+ }
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
}
@@ -255,8 +284,13 @@
public void startInput(IBinder startInputToken, IInputContext inputContext,
@InputConnectionInspector.MissingMethodFlags final int missingMethods,
EditorInfo attribute, boolean restarting) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_START_INPUT,
- missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute));
+ if (mIsUnbindIssued == null) {
+ Log.e(TAG, "startInput must be called after bindInput.");
+ mIsUnbindIssued = new AtomicBoolean();
+ }
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT,
+ missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute,
+ mIsUnbindIssued));
}
@BinderThread
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 3c868c3..903b602 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -16,14 +16,14 @@
package android.net;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
-import libcore.net.http.Dns;
-import libcore.net.http.HttpURLConnectionFactory;
+import com.android.okhttp.internalandroidapi.Dns;
+import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -34,11 +34,12 @@
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
-import java.net.UnknownHostException;
import java.net.URL;
import java.net.URLConnection;
+import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
+
import javax.net.SocketFactory;
/**
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 4bb8844..db12dd9 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,6 +24,8 @@
import com.android.internal.util.BitUtils;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.StringJoiner;
@@ -77,6 +80,31 @@
*/
private long mNetworkCapabilities;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "NET_CAPABILITY_" }, value = {
+ NET_CAPABILITY_MMS,
+ NET_CAPABILITY_SUPL,
+ NET_CAPABILITY_DUN,
+ NET_CAPABILITY_FOTA,
+ NET_CAPABILITY_IMS,
+ NET_CAPABILITY_CBS,
+ NET_CAPABILITY_WIFI_P2P,
+ NET_CAPABILITY_IA,
+ NET_CAPABILITY_RCS,
+ NET_CAPABILITY_XCAP,
+ NET_CAPABILITY_EIMS,
+ NET_CAPABILITY_NOT_METERED,
+ NET_CAPABILITY_INTERNET,
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_TRUSTED,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_CAPTIVE_PORTAL,
+ NET_CAPABILITY_FOREGROUND,
+ })
+ public @interface NetCapability { }
+
/**
* Indicates this is a network that has the ability to reach the
* carrier's MMSC for sending and receiving MMS messages.
@@ -260,11 +288,11 @@
* Multiple capabilities may be applied sequentially. Note that when searching
* for a network to satisfy a request, all capabilities requested must be satisfied.
*
- * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be added.
+ * @param capability the capability to be added.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities addCapability(int capability) {
+ public NetworkCapabilities addCapability(@NetCapability int capability) {
if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
throw new IllegalArgumentException("NetworkCapability out of range");
}
@@ -275,11 +303,11 @@
/**
* Removes (if found) the given capability from this {@code NetworkCapability} instance.
*
- * @param capability the {@code NetworkCapabilities.NET_CAPABILTIY_*} to be removed.
+ * @param capability the capability to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities removeCapability(int capability) {
+ public NetworkCapabilities removeCapability(@NetCapability int capability) {
if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
throw new IllegalArgumentException("NetworkCapability out of range");
}
@@ -290,21 +318,20 @@
/**
* Gets all the capabilities set on this {@code NetworkCapability} instance.
*
- * @return an array of {@code NetworkCapabilities.NET_CAPABILITY_*} values
- * for this instance.
+ * @return an array of capability values for this instance.
* @hide
*/
- public int[] getCapabilities() {
+ public @NetCapability int[] getCapabilities() {
return BitUtils.unpackBits(mNetworkCapabilities);
}
/**
* Tests for the presence of a capabilitity on this instance.
*
- * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be tested for.
+ * @param capability the capabilities to be tested for.
* @return {@code true} if set on this instance.
*/
- public boolean hasCapability(int capability) {
+ public boolean hasCapability(@NetCapability int capability) {
if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
return false;
}
@@ -385,6 +412,19 @@
*/
private long mTransportTypes;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "TRANSPORT_" }, value = {
+ TRANSPORT_CELLULAR,
+ TRANSPORT_WIFI,
+ TRANSPORT_BLUETOOTH,
+ TRANSPORT_ETHERNET,
+ TRANSPORT_VPN,
+ TRANSPORT_WIFI_AWARE,
+ TRANSPORT_LOWPAN,
+ })
+ public @interface Transport { }
+
/**
* Indicates this network uses a Cellular transport.
*/
@@ -426,7 +466,7 @@
public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
/** @hide */
- public static boolean isValidTransport(int transportType) {
+ public static boolean isValidTransport(@Transport int transportType) {
return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT);
}
@@ -449,11 +489,11 @@
* to be selected. This is logically different than
* {@code NetworkCapabilities.NET_CAPABILITY_*} listed above.
*
- * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be added.
+ * @param transportType the transport type to be added.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities addTransportType(int transportType) {
+ public NetworkCapabilities addTransportType(@Transport int transportType) {
checkValidTransportType(transportType);
mTransportTypes |= 1 << transportType;
setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -463,11 +503,11 @@
/**
* Removes (if found) the given transport from this {@code NetworkCapability} instance.
*
- * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be removed.
+ * @param transportType the transport type to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities removeTransportType(int transportType) {
+ public NetworkCapabilities removeTransportType(@Transport int transportType) {
checkValidTransportType(transportType);
mTransportTypes &= ~(1 << transportType);
setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -477,21 +517,20 @@
/**
* Gets all the transports set on this {@code NetworkCapability} instance.
*
- * @return an array of {@code NetworkCapabilities.TRANSPORT_*} values
- * for this instance.
+ * @return an array of transport type values for this instance.
* @hide
*/
- public int[] getTransportTypes() {
+ public @Transport int[] getTransportTypes() {
return BitUtils.unpackBits(mTransportTypes);
}
/**
* Tests for the presence of a transport on this instance.
*
- * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for.
+ * @param transportType the transport type to be tested for.
* @return {@code true} if set on this instance.
*/
- public boolean hasTransport(int transportType) {
+ public boolean hasTransport(@Transport int transportType) {
return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0);
}
@@ -896,7 +935,7 @@
/**
* @hide
*/
- public static String capabilityNamesOf(int[] capabilities) {
+ public static String capabilityNamesOf(@NetCapability int[] capabilities) {
StringJoiner joiner = new StringJoiner("|");
if (capabilities != null) {
for (int c : capabilities) {
@@ -909,7 +948,7 @@
/**
* @hide
*/
- public static String capabilityNameOf(int capability) {
+ public static String capabilityNameOf(@NetCapability int capability) {
switch (capability) {
case NET_CAPABILITY_MMS: return "MMS";
case NET_CAPABILITY_SUPL: return "SUPL";
@@ -937,7 +976,7 @@
/**
* @hide
*/
- public static String transportNamesOf(int[] types) {
+ public static String transportNamesOf(@Transport int[] types) {
StringJoiner joiner = new StringJoiner("|");
if (types != null) {
for (int t : types) {
@@ -950,14 +989,14 @@
/**
* @hide
*/
- public static String transportNameOf(int transport) {
+ public static String transportNameOf(@Transport int transport) {
if (!isValidTransport(transport)) {
return "UNKNOWN";
}
return TRANSPORT_NAMES[transport];
}
- private static void checkValidTransportType(int transport) {
+ private static void checkValidTransportType(@Transport int transport) {
Preconditions.checkArgument(
isValidTransport(transport), "Invalid TransportType " + transport);
}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 95a8bb4..25b1705 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -155,14 +155,13 @@
* 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_CAPABILITY_*}
- * definitions.
+ * satisfied.
*
- * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to add.
+ * @param capability The capability to add.
* @return The builder to facilitate chaining
* {@code builder.addCapability(...).addCapability();}.
*/
- public Builder addCapability(int capability) {
+ public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.addCapability(capability);
return this;
}
@@ -170,10 +169,10 @@
/**
* Removes (if found) the given capability from this builder instance.
*
- * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to remove.
+ * @param capability The capability to remove.
* @return The builder to facilitate chaining.
*/
- public Builder removeCapability(int capability) {
+ public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.removeCapability(capability);
return this;
}
@@ -208,13 +207,12 @@
* Adds the given transport requirement to this builder. These represent
* the set of allowed transports for the request. Only networks using one
* of these transports will satisfy the request. If no particular transports
- * are required, none should be specified here. See {@link NetworkCapabilities}
- * for {@code TRANSPORT_*} definitions.
+ * are required, none should be specified here.
*
- * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to add.
+ * @param transportType The transport type to add.
* @return The builder to facilitate chaining.
*/
- public Builder addTransportType(int transportType) {
+ public Builder addTransportType(@NetworkCapabilities.Transport int transportType) {
mNetworkCapabilities.addTransportType(transportType);
return this;
}
@@ -222,10 +220,10 @@
/**
* Removes (if found) the given transport from this builder instance.
*
- * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to remove.
+ * @param transportType The transport type to remove.
* @return The builder to facilitate chaining.
*/
- public Builder removeTransportType(int transportType) {
+ public Builder removeTransportType(@NetworkCapabilities.Transport int transportType) {
mNetworkCapabilities.removeTransportType(transportType);
return this;
}
diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS
index 0f1e259..d1ce60e 100644
--- a/core/java/android/net/OWNERS
+++ b/core/java/android/net/OWNERS
@@ -1,6 +1,6 @@
ek@google.com
hugobenichi@google.com
-jsharkey@google.com
+jsharkey@android.com
lorenzo@google.com
satk@google.com
silberst@google.com
diff --git a/core/java/android/net/metrics/ConnectStats.java b/core/java/android/net/metrics/ConnectStats.java
index 30b2656..2495cab 100644
--- a/core/java/android/net/metrics/ConnectStats.java
+++ b/core/java/android/net/metrics/ConnectStats.java
@@ -20,6 +20,7 @@
import android.system.OsConstants;
import android.util.IntArray;
import android.util.SparseIntArray;
+
import com.android.internal.util.BitUtils;
import com.android.internal.util.TokenBucket;
@@ -43,6 +44,8 @@
public final TokenBucket mLatencyTb;
/** Maximum number of latency values recorded. */
public final int mMaxLatencyRecords;
+ /** Total count of events */
+ public int eventCount = 0;
/** Total count of successful connects. */
public int connectCount = 0;
/** Total count of successful connects done in blocking mode. */
@@ -57,12 +60,15 @@
mMaxLatencyRecords = maxLatencyRecords;
}
- public void addEvent(int errno, int latencyMs, String ipAddr) {
+ boolean addEvent(int errno, int latencyMs, String ipAddr) {
+ eventCount++;
if (isSuccess(errno)) {
countConnect(errno, ipAddr);
countLatency(errno, latencyMs);
+ return true;
} else {
countError(errno);
+ return false;
}
}
@@ -101,7 +107,7 @@
return (errno == 0) || isNonBlocking(errno);
}
- private static boolean isNonBlocking(int errno) {
+ static boolean isNonBlocking(int errno) {
// On non-blocking TCP sockets, connect() immediately returns EINPROGRESS.
// On non-blocking TCP sockets that are connecting, connect() immediately returns EALREADY.
return (errno == EINPROGRESS) || (errno == EALREADY);
@@ -117,6 +123,7 @@
for (int t : BitUtils.unpackBits(transports)) {
builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
}
+ builder.append(String.format("%d events, ", eventCount));
builder.append(String.format("%d success, ", connectCount));
builder.append(String.format("%d blocking, ", connectBlockingCount));
builder.append(String.format("%d IPv6 dst", ipv6ConnectCount));
diff --git a/core/java/android/net/metrics/DefaultNetworkEvent.java b/core/java/android/net/metrics/DefaultNetworkEvent.java
index 28cf42f..eb61c153 100644
--- a/core/java/android/net/metrics/DefaultNetworkEvent.java
+++ b/core/java/android/net/metrics/DefaultNetworkEvent.java
@@ -16,73 +16,43 @@
package android.net.metrics;
+import static android.net.ConnectivityManager.NETID_UNSET;
+
import android.net.NetworkCapabilities;
-import android.os.Parcel;
-import android.os.Parcelable;
/**
* An event recorded by ConnectivityService when there is a change in the default network.
* {@hide}
*/
-public final class DefaultNetworkEvent implements Parcelable {
+public class DefaultNetworkEvent {
+
// The ID of the network that has become the new default or NETID_UNSET if none.
- public final int netId;
+ public int netId = NETID_UNSET;
// The list of transport types of the new default network, for example TRANSPORT_WIFI, as
// defined in NetworkCapabilities.java.
- public final int[] transportTypes;
+ public int[] transportTypes = new int[0];
// The ID of the network that was the default before or NETID_UNSET if none.
- public final int prevNetId;
+ public int prevNetId = NETID_UNSET;
// Whether the previous network had IPv4/IPv6 connectivity.
- public final boolean prevIPv4;
- public final boolean prevIPv6;
-
- public DefaultNetworkEvent(int netId, int[] transportTypes,
- int prevNetId, boolean prevIPv4, boolean prevIPv6) {
- this.netId = netId;
- this.transportTypes = transportTypes;
- this.prevNetId = prevNetId;
- this.prevIPv4 = prevIPv4;
- this.prevIPv6 = prevIPv6;
- }
-
- private DefaultNetworkEvent(Parcel in) {
- this.netId = in.readInt();
- this.transportTypes = in.createIntArray();
- this.prevNetId = in.readInt();
- this.prevIPv4 = (in.readByte() > 0);
- this.prevIPv6 = (in.readByte() > 0);
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(netId);
- out.writeIntArray(transportTypes);
- out.writeInt(prevNetId);
- out.writeByte(prevIPv4 ? (byte) 1 : (byte) 0);
- out.writeByte(prevIPv6 ? (byte) 1 : (byte) 0);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
+ public boolean prevIPv4;
+ public boolean prevIPv6;
@Override
public String toString() {
- String prevNetwork = String.valueOf(prevNetId);
- String newNetwork = String.valueOf(netId);
- if (prevNetId != 0) {
- prevNetwork += ":" + ipSupport();
- }
- if (netId != 0) {
- newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
- }
- return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
+ String prevNetwork = String.valueOf(prevNetId);
+ String newNetwork = String.valueOf(netId);
+ if (prevNetId != 0) {
+ prevNetwork += ":" + ipSupport();
+ }
+ if (netId != 0) {
+ newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
+ }
+ return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
}
private String ipSupport() {
if (prevIPv4 && prevIPv6) {
- return "DUAL";
+ return "IPv4v6";
}
if (prevIPv6) {
return "IPv6";
@@ -92,15 +62,4 @@
}
return "NONE";
}
-
- public static final Parcelable.Creator<DefaultNetworkEvent> CREATOR
- = new Parcelable.Creator<DefaultNetworkEvent>() {
- public DefaultNetworkEvent createFromParcel(Parcel in) {
- return new DefaultNetworkEvent(in);
- }
-
- public DefaultNetworkEvent[] newArray(int size) {
- return new DefaultNetworkEvent[size];
- }
- };
}
diff --git a/core/java/android/net/metrics/DnsEvent.java b/core/java/android/net/metrics/DnsEvent.java
index a4970e4..81b098b 100644
--- a/core/java/android/net/metrics/DnsEvent.java
+++ b/core/java/android/net/metrics/DnsEvent.java
@@ -17,11 +17,13 @@
package android.net.metrics;
import android.net.NetworkCapabilities;
-import java.util.Arrays;
+
import com.android.internal.util.BitUtils;
+import java.util.Arrays;
+
/**
- * A DNS event recorded by NetdEventListenerService.
+ * A batch of DNS events recorded by NetdEventListenerService for a specific network.
* {@hide}
*/
final public class DnsEvent {
@@ -38,6 +40,8 @@
// the eventTypes, returnCodes, and latenciesMs arrays have the same length and the i-th event
// is spread across the three array at position i.
public int eventCount;
+ // The number of successful DNS queries recorded.
+ public int successCount;
// The types of DNS queries as defined in INetdEventListener.
public byte[] eventTypes;
// Current getaddrinfo codes go from 1 to EAI_MAX = 15. gethostbyname returns errno, but there
@@ -54,10 +58,11 @@
latenciesMs = new int[initialCapacity];
}
- public void addResult(byte eventType, byte returnCode, int latencyMs) {
+ boolean addResult(byte eventType, byte returnCode, int latencyMs) {
+ boolean isSuccess = (returnCode == 0);
if (eventCount >= SIZE_LIMIT) {
// TODO: implement better rate limiting that does not biases metrics.
- return;
+ return isSuccess;
}
if (eventCount == eventTypes.length) {
resize((int) (1.4 * eventCount));
@@ -66,6 +71,10 @@
returnCodes[eventCount] = returnCode;
latenciesMs[eventCount] = latencyMs;
eventCount++;
+ if (isSuccess) {
+ successCount++;
+ }
+ return isSuccess;
}
public void resize(int newLength) {
@@ -80,6 +89,8 @@
for (int t : BitUtils.unpackBits(transports)) {
builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
}
- return builder.append(eventCount).append(" events)").toString();
+ builder.append(String.format("%d events, ", eventCount));
+ builder.append(String.format("%d success)", successCount));
+ return builder.toString();
}
}
diff --git a/core/java/android/net/metrics/NetworkMetrics.java b/core/java/android/net/metrics/NetworkMetrics.java
new file mode 100644
index 0000000..2b662a0
--- /dev/null
+++ b/core/java/android/net/metrics/NetworkMetrics.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics;
+
+import android.net.NetworkCapabilities;
+
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.TokenBucket;
+
+import java.util.StringJoiner;
+
+/**
+ * A class accumulating network metrics received from Netd regarding dns queries and
+ * connect() calls on a given network.
+ *
+ * This class also accumulates running sums of dns and connect latency stats and
+ * error counts for bug report logging.
+ *
+ * @hide
+ */
+public class NetworkMetrics {
+
+ private static final int INITIAL_DNS_BATCH_SIZE = 100;
+ private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
+
+ // The network id of the Android Network.
+ public final int netId;
+ // The transport types bitmap of the Android Network, as defined in NetworkCapabilities.java.
+ public final long transports;
+ // Accumulated metrics for connect events.
+ public final ConnectStats connectMetrics;
+ // Accumulated metrics for dns events.
+ public final DnsEvent dnsMetrics;
+ // Running sums of latencies and error counts for connect and dns events.
+ public final Summary summary;
+ // Running sums of the most recent latencies and error counts for connect and dns events.
+ // Starts null until some events are accumulated.
+ // Allows to collect periodic snapshot of the running summaries for a given network.
+ public Summary pendingSummary;
+
+ public NetworkMetrics(int netId, long transports, TokenBucket tb) {
+ this.netId = netId;
+ this.transports = transports;
+ this.connectMetrics =
+ new ConnectStats(netId, transports, tb, CONNECT_LATENCY_MAXIMUM_RECORDS);
+ this.dnsMetrics = new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
+ this.summary = new Summary(netId, transports);
+ }
+
+ /**
+ * Get currently pending Summary statistics, if any, for this NetworkMetrics, merge them
+ * into the long running Summary statistics of this NetworkMetrics, and also clear them.
+ */
+ public Summary getPendingStats() {
+ Summary s = pendingSummary;
+ pendingSummary = null;
+ if (s != null) {
+ summary.merge(s);
+ }
+ return s;
+ }
+
+ /** Accumulate a dns query result reported by netd. */
+ public void addDnsResult(int eventType, int returnCode, int latencyMs) {
+ if (pendingSummary == null) {
+ pendingSummary = new Summary(netId, transports);
+ }
+ boolean isSuccess = dnsMetrics.addResult((byte) eventType, (byte) returnCode, latencyMs);
+ pendingSummary.dnsLatencies.count(latencyMs);
+ pendingSummary.dnsErrorRate.count(isSuccess ? 0 : 1);
+ }
+
+ /** Accumulate a connect query result reported by netd. */
+ public void addConnectResult(int error, int latencyMs, String ipAddr) {
+ if (pendingSummary == null) {
+ pendingSummary = new Summary(netId, transports);
+ }
+ boolean isSuccess = connectMetrics.addEvent(error, latencyMs, ipAddr);
+ pendingSummary.connectErrorRate.count(isSuccess ? 0 : 1);
+ if (ConnectStats.isNonBlocking(error)) {
+ pendingSummary.connectLatencies.count(latencyMs);
+ }
+ }
+
+ /** Represents running sums for dns and connect average error counts and average latencies. */
+ public static class Summary {
+
+ public final int netId;
+ public final long transports;
+ // DNS latencies measured in milliseconds.
+ public final Metrics dnsLatencies = new Metrics();
+ // DNS error rate measured in percentage points.
+ public final Metrics dnsErrorRate = new Metrics();
+ // Blocking connect latencies measured in milliseconds.
+ public final Metrics connectLatencies = new Metrics();
+ // Blocking and non blocking connect error rate measured in percentage points.
+ public final Metrics connectErrorRate = new Metrics();
+
+ public Summary(int netId, long transports) {
+ this.netId = netId;
+ this.transports = transports;
+ }
+
+ void merge(Summary that) {
+ dnsLatencies.merge(that.dnsLatencies);
+ dnsErrorRate.merge(that.dnsErrorRate);
+ connectLatencies.merge(that.connectLatencies);
+ connectErrorRate.merge(that.connectErrorRate);
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner j = new StringJoiner(", ", "{", "}");
+ j.add("netId=" + netId);
+ for (int t : BitUtils.unpackBits(transports)) {
+ j.add(NetworkCapabilities.transportNameOf(t));
+ }
+ j.add(String.format("dns avg=%dms max=%dms err=%.1f%% tot=%d",
+ (int) dnsLatencies.average(), (int) dnsLatencies.max,
+ 100 * dnsErrorRate.average(), dnsErrorRate.count));
+ j.add(String.format("connect avg=%dms max=%dms err=%.1f%% tot=%d",
+ (int) connectLatencies.average(), (int) connectLatencies.max,
+ 100 * connectErrorRate.average(), connectErrorRate.count));
+ return j.toString();
+ }
+ }
+
+ /** Tracks a running sum and returns the average of a metric. */
+ static class Metrics {
+ public double sum;
+ public double max = Double.MIN_VALUE;
+ public int count;
+
+ void merge(Metrics that) {
+ this.count += that.count;
+ this.sum += that.sum;
+ this.max = Math.max(this.max, that.max);
+ }
+
+ void count(double value) {
+ count++;
+ sum += value;
+ max = Math.max(max, value);
+ }
+
+ double average() {
+ double a = sum / (double) count;
+ if (Double.isNaN(a)) {
+ a = 0;
+ }
+ return a;
+ }
+ }
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 8682c01..a8bd940 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1986,7 +1986,7 @@
public abstract long getDeviceIdlingTime(int mode, long elapsedRealtimeUs, int which);
/**
- * Returns the number of times that the devie has started idling.
+ * Returns the number of times that the device has started idling.
*
* {@hide}
*/
@@ -6453,7 +6453,7 @@
pw.println();
}
}
- if (!filtering || (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0) {
+ if (!filtering || (flags & DUMP_DAILY_ONLY) != 0) {
pw.println("Daily stats:");
pw.print(" Current start time: ");
pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
diff --git a/core/java/android/os/BatteryStatsInternal.java b/core/java/android/os/BatteryStatsInternal.java
new file mode 100644
index 0000000..b0436eb
--- /dev/null
+++ b/core/java/android/os/BatteryStatsInternal.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Battery stats local system service interface. This is used to pass internal data out of
+ * BatteryStatsImpl.
+ *
+ * @hide Only for use within Android OS.
+ */
+public abstract class BatteryStatsInternal {
+ /**
+ * Returns the wifi interfaces.
+ */
+ public abstract String[] getWifiIfaces();
+
+ /**
+ * Returns the mobile data interfaces.
+ */
+ public abstract String[] getMobileIfaces();
+}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index e9e695b..2bfb013 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -27,12 +27,14 @@
import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
import libcore.io.IoUtils;
+import libcore.util.NativeAllocationRegistry;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
+import java.util.ArrayList;
/**
* Base class for a remotable object, the core part of a lightweight
@@ -90,6 +92,20 @@
*/
private static volatile TransactionTracker sTransactionTracker = null;
+ /**
+ * Guestimate of native memory associated with a Binder.
+ */
+ private static final int NATIVE_ALLOCATION_SIZE = 500;
+
+ private static native long getNativeFinalizer();
+
+ // Use a Holder to allow static initialization of Binder in the boot image, and
+ // possibly to avoid some initialization ordering issues.
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ Binder.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+ }
+
// Transaction tracking code.
/**
@@ -167,7 +183,7 @@
try {
if (binder instanceof BinderProxy) {
((BinderProxy) binder).mWarnOnBlocking = false;
- } else if (binder != null
+ } else if (binder != null && binder.getInterfaceDescriptor() != null
&& binder.queryLocalInterface(binder.getInterfaceDescriptor()) == null) {
Log.w(TAG, "Unable to allow blocking on interface " + binder);
}
@@ -188,8 +204,11 @@
}
}
- /* mObject is used by native code, do not remove or rename */
- private long mObject;
+ /**
+ * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null.
+ */
+ private final long mObject;
+
private IInterface mOwner;
private String mDescriptor;
@@ -360,7 +379,8 @@
* Default constructor initializes the object.
*/
public Binder() {
- init();
+ mObject = getNativeBBinderHolder();
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject);
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Binder> klass = getClass();
@@ -414,7 +434,7 @@
* descriptor.
*/
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
- if (mDescriptor.equals(descriptor)) {
+ if (mDescriptor != null && mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
@@ -643,14 +663,6 @@
return true;
}
- protected void finalize() throws Throwable {
- try {
- destroyBinder();
- } finally {
- super.finalize();
- }
- }
-
static void checkParcel(IBinder obj, int code, Parcel parcel, String msg) {
if (CHECK_PARCEL_SIZE && parcel.dataSize() >= 800*1024) {
// Trying to send > 800k, this is way too much
@@ -674,8 +686,8 @@
}
}
- private native final void init();
- private native final void destroyBinder();
+ private static native long getNativeBBinderHolder();
+ private static native long getFinalizer();
// Entry point from android_util_Binder.cpp's onTransact
private boolean execTransact(int code, long dataObj, long replyObj,
@@ -736,11 +748,207 @@
*/
final class BinderProxy implements IBinder {
// See android_util_Binder.cpp for the native half of this.
- // TODO: Consider using NativeAllocationRegistry instead of finalization.
// Assume the process-wide default value when created
volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
+ /*
+ * Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies.
+ * We roll our own only because we need to lazily remove WeakReferences during accesses
+ * to avoid accumulating junk WeakReference objects. WeakHashMap isn't easily usable
+ * because we want weak values, not keys.
+ * Our hash table is never resized, but the number of entries is unlimited;
+ * performance degrades as occupancy increases significantly past MAIN_INDEX_SIZE.
+ * Not thread-safe. Client ensures there's a single access at a time.
+ */
+ private static final class ProxyMap {
+ private static final int LOG_MAIN_INDEX_SIZE = 8;
+ private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
+ private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
+
+ /**
+ * We next warn when we exceed this bucket size.
+ */
+ private int mWarnBucketSize = 20;
+
+ /**
+ * Increment mWarnBucketSize by WARN_INCREMENT each time we warn.
+ */
+ private static final int WARN_INCREMENT = 10;
+
+ /**
+ * Hash function tailored to native pointers.
+ * Returns a value < MAIN_INDEX_SIZE.
+ */
+ private static int hash(long arg) {
+ return ((int) ((arg >> 2) ^ (arg >> (2 + LOG_MAIN_INDEX_SIZE)))) & MAIN_INDEX_MASK;
+ }
+
+ /**
+ * Return the total number of pairs in the map.
+ */
+ int size() {
+ int size = 0;
+ for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+ if (a != null) {
+ size += a.size();
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Remove ith entry from the hash bucket indicated by hash.
+ */
+ private void remove(int hash, int index) {
+ Long[] keyArray = mMainIndexKeys[hash];
+ ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[hash];
+ int size = valueArray.size(); // KeyArray may have extra elements.
+ // Move last entry into empty slot, and truncate at end.
+ if (index != size - 1) {
+ keyArray[index] = keyArray[size - 1];
+ valueArray.set(index, valueArray.get(size - 1));
+ }
+ valueArray.remove(size - 1);
+ // Just leave key array entry; it's unused. We only trust the valueArray size.
+ }
+
+ /**
+ * Look up the supplied key. If we have a non-cleared entry for it, return it.
+ */
+ BinderProxy get(long key) {
+ int myHash = hash(key);
+ Long[] keyArray = mMainIndexKeys[myHash];
+ if (keyArray == null) {
+ return null;
+ }
+ ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+ int bucketSize = valueArray.size();
+ for (int i = 0; i < bucketSize; ++i) {
+ long foundKey = keyArray[i];
+ if (key == foundKey) {
+ WeakReference<BinderProxy> wr = valueArray.get(i);
+ BinderProxy bp = wr.get();
+ if (bp != null) {
+ return bp;
+ } else {
+ remove(myHash, i);
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ private int mRandom; // A counter used to generate a "random" index. World's 2nd worst RNG.
+
+ /**
+ * Add the key-value pair to the map.
+ * Requires that the indicated key is not already in the map.
+ */
+ void set(long key, @NonNull BinderProxy value) {
+ int myHash = hash(key);
+ ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+ if (valueArray == null) {
+ valueArray = mMainIndexValues[myHash] = new ArrayList<>();
+ mMainIndexKeys[myHash] = new Long[1];
+ }
+ int size = valueArray.size();
+ WeakReference<BinderProxy> newWr = new WeakReference<>(value);
+ // First look for a cleared reference.
+ // This ensures that ArrayList size is bounded by the maximum occupancy of
+ // that bucket.
+ for (int i = 0; i < size; ++i) {
+ if (valueArray.get(i).get() == null) {
+ valueArray.set(i, newWr);
+ Long[] keyArray = mMainIndexKeys[myHash];
+ keyArray[i] = key;
+ if (i < size - 1) {
+ // "Randomly" check one of the remaining entries in [i+1, size), so that
+ // needlessly long buckets are eventually pruned.
+ int rnd = Math.floorMod(++mRandom, size - (i + 1));
+ if (valueArray.get(i + 1 + rnd).get() == null) {
+ remove(myHash, i + 1 + rnd);
+ }
+ }
+ return;
+ }
+ }
+ valueArray.add(size, newWr);
+ Long[] keyArray = mMainIndexKeys[myHash];
+ if (keyArray.length == size) {
+ // size >= 1, since we initially allocated one element
+ Long[] newArray = new Long[size + size / 2 + 2];
+ System.arraycopy(keyArray, 0, newArray, 0, size);
+ newArray[size] = key;
+ mMainIndexKeys[myHash] = newArray;
+ } else {
+ keyArray[size] = key;
+ }
+ if (size >= mWarnBucketSize) {
+ Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size
+ + " total = " + size());
+ mWarnBucketSize += WARN_INCREMENT;
+ }
+ }
+
+ // Corresponding ArrayLists in the following two arrays always have the same size.
+ // They contain no empty entries. However WeakReferences in the values ArrayLists
+ // may have been cleared.
+
+ // mMainIndexKeys[i][j] corresponds to mMainIndexValues[i].get(j) .
+ // The values ArrayList has the proper size(), the corresponding keys array
+ // is always at least the same size, but may be larger.
+ // If either a particular keys array, or the corresponding values ArrayList
+ // are null, then they both are.
+ private final Long[][] mMainIndexKeys = new Long[MAIN_INDEX_SIZE][];
+ private final ArrayList<WeakReference<BinderProxy>>[] mMainIndexValues =
+ new ArrayList[MAIN_INDEX_SIZE];
+ }
+
+ private static ProxyMap sProxyMap = new ProxyMap();
+
+ /**
+ * Return a BinderProxy for IBinder.
+ * This method is thread-hostile! The (native) caller serializes getInstance() calls using
+ * gProxyLock.
+ * If we previously returned a BinderProxy bp for the same iBinder, and bp is still
+ * in use, then we return the same bp.
+ *
+ * @param nativeData C++ pointer to (possibly still empty) BinderProxyNativeData.
+ * Takes ownership of nativeData iff <result>.mNativeData == nativeData. Caller will usually
+ * delete nativeData if that's not the case.
+ * @param iBinder C++ pointer to IBinder. Does not take ownership of referenced object.
+ */
+ private static BinderProxy getInstance(long nativeData, long iBinder) {
+ BinderProxy result = sProxyMap.get(iBinder);
+ if (result == null) {
+ result = new BinderProxy(nativeData);
+ sProxyMap.set(iBinder, result);
+ }
+ return result;
+ }
+
+ private BinderProxy(long nativeData) {
+ mNativeData = nativeData;
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeData);
+ }
+
+ /**
+ * Guestimate of native memory associated with a BinderProxy.
+ * This includes the underlying IBinder, associated DeathRecipientList, and KeyedVector
+ * that points back to us. We guess high since it includes a GlobalRef, which
+ * may be in short supply.
+ */
+ private static final int NATIVE_ALLOCATION_SIZE = 1000;
+
+ // Use a Holder to allow static initialization of BinderProxy in the boot image, and
+ // to avoid some initialization ordering issues.
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ BinderProxy.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+ }
+
public native boolean pingBinder();
public native boolean isBinderAlive();
@@ -776,6 +984,7 @@
}
}
+ private static native long getNativeFinalizer();
public native String getInterfaceDescriptor() throws RemoteException;
public native boolean transactNative(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
@@ -830,21 +1039,6 @@
}
}
- BinderProxy() {
- mSelf = new WeakReference(this);
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- destroy();
- } finally {
- super.finalize();
- }
- }
-
- private native final void destroy();
-
private static final void sendDeathNotice(DeathRecipient recipient) {
if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
try {
@@ -856,19 +1050,9 @@
}
}
- // This WeakReference to "this" is used only by native code to "attach" to the
- // native IBinder object.
- // Using WeakGlobalRefs instead currently appears unsafe, in that they can yield a
- // non-null value after the BinderProxy is enqueued for finalization.
- // Used only once immediately after construction.
- // TODO: Consider making the extra native-to-java call to compute this on the fly.
- final private WeakReference mSelf;
-
- // Native pointer to the wrapped native IBinder object. Counted as strong reference.
- private long mObject;
-
- // Native pointer to native DeathRecipientList. Counted as strong reference.
- // Basically owned by the JavaProxy object. Reference counted only because DeathRecipients
- // hold a weak reference that can be temporarily promoted.
- private long mOrgue;
+ /**
+ * C++ pointer to BinderProxyNativeData. That consists of strong pointers to the
+ * native IBinder object, and a DeathRecipientList.
+ */
+ private final long mNativeData;
}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 017c213..2e62eb6 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -23,7 +23,6 @@
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.TypedProperties;
-import dalvik.bytecode.OpcodeInfo;
import dalvik.system.VMDebug;
import org.apache.harmony.dalvik.ddmc.Chunk;
@@ -48,8 +47,6 @@
import java.util.Map;
-
-
/**
* Provides various debugging methods for Android applications, including
* tracing and allocation counts.
@@ -1959,13 +1956,7 @@
*/
@Deprecated
public static class InstructionCount {
- private static final int NUM_INSTR =
- OpcodeInfo.MAXIMUM_PACKED_VALUE + 1;
-
- private int[] mCounts;
-
public InstructionCount() {
- mCounts = new int[NUM_INSTR];
}
/**
@@ -1975,13 +1966,7 @@
* @return true if counting was started
*/
public boolean resetAndStart() {
- try {
- VMDebug.startInstructionCounting();
- VMDebug.resetInstructionCount();
- } catch (UnsupportedOperationException uoe) {
- return false;
- }
- return true;
+ return false;
}
/**
@@ -1989,13 +1974,7 @@
* counting process.
*/
public boolean collect() {
- try {
- VMDebug.stopInstructionCounting();
- VMDebug.getInstructionCount(mCounts);
- } catch (UnsupportedOperationException uoe) {
- return false;
- }
- return true;
+ return false;
}
/**
@@ -2003,13 +1982,7 @@
* all threads).
*/
public int globalTotal() {
- int count = 0;
-
- for (int i = 0; i < NUM_INSTR; i++) {
- count += mCounts[i];
- }
-
- return count;
+ return 0;
}
/**
@@ -2017,15 +1990,7 @@
* executed globally.
*/
public int globalMethodInvocations() {
- int count = 0;
-
- for (int i = 0; i < NUM_INSTR; i++) {
- if (OpcodeInfo.isInvoke(i)) {
- count += mCounts[i];
- }
- }
-
- return count;
+ return 0;
}
}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 5b0e5bbc..f977c1d 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -836,7 +836,6 @@
* physically removed.
*/
public static boolean isExternalStorageRemovable() {
- if (isStorageDisabled()) return false;
final File externalDir = sCurrentUser.getExternalDirs()[0];
return isExternalStorageRemovable(externalDir);
}
@@ -875,7 +874,6 @@
* boolean)
*/
public static boolean isExternalStorageEmulated() {
- if (isStorageDisabled()) return false;
final File externalDir = sCurrentUser.getExternalDirs()[0];
return isExternalStorageEmulated(externalDir);
}
@@ -951,9 +949,6 @@
return cur;
}
- private static boolean isStorageDisabled() {
- return SystemProperties.getBoolean("config.disable_storage", false);
- }
/**
* If the given path exists on emulated external storage, return the
diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java
index 7dec4d7..3544ea1 100644
--- a/core/java/android/os/HidlSupport.java
+++ b/core/java/android/os/HidlSupport.java
@@ -156,4 +156,27 @@
// Should not reach here.
throw new UnsupportedOperationException();
}
+
+ /**
+ * Test that two interfaces are equal. This is the Java equivalent to C++
+ * interfacesEqual function.
+ * This essentially calls .equals on the internal binder objects (via Binder()).
+ * - If both interfaces are proxies, asBinder() returns a {@link HwRemoteBinder}
+ * object, and they are compared in {@link HwRemoteBinder#equals}.
+ * - If both interfaces are stubs, asBinder() returns the object itself. By default,
+ * auto-generated IFoo.Stub does not override equals(), but an implementation can
+ * optionally override it, and {@code interfacesEqual} will use it here.
+ */
+ public static boolean interfacesEqual(IHwInterface lft, Object rgt) {
+ if (lft == rgt) {
+ return true;
+ }
+ if (lft == null || rgt == null) {
+ return false;
+ }
+ if (!(rgt instanceof IHwInterface)) {
+ return false;
+ }
+ return Objects.equals(lft.asBinder(), ((IHwInterface) rgt).asBinder());
+ }
}
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index 270e63f..dd9e774 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -16,10 +16,10 @@
package android.os;
-import java.util.ArrayList;
-import java.util.NoSuchElementException;
import libcore.util.NativeAllocationRegistry;
+import java.util.NoSuchElementException;
+
/** @hide */
public abstract class HwBinder implements IHwBinder {
private static final String TAG = "HwBinder";
@@ -46,9 +46,16 @@
public native final void registerService(String serviceName)
throws RemoteException;
- public static native final IHwBinder getService(
+ public static final IHwBinder getService(
String iface,
String serviceName)
+ throws RemoteException, NoSuchElementException {
+ return getService(iface, serviceName, false /* retry */);
+ }
+ public static native final IHwBinder getService(
+ String iface,
+ String serviceName,
+ boolean retry)
throws RemoteException, NoSuchElementException;
public static native final void configureRpcThreadpool(
@@ -56,6 +63,13 @@
public static native final void joinRpcThreadpool();
+ /**
+ * Call configureRpcThreadpool, then actually spawn
+ * (maxThreads - (callerWillJoin ? 0 : 1)) threads.
+ */
+ public static final native void startRpcThreadPool(
+ long maxThreads, boolean callerWillJoin);
+
// Returns address of the "freeFunction".
private static native final long native_init();
diff --git a/core/java/android/os/HwBlob.java b/core/java/android/os/HwBlob.java
index 88226f0..5e9b9ae3 100644
--- a/core/java/android/os/HwBlob.java
+++ b/core/java/android/os/HwBlob.java
@@ -43,6 +43,18 @@
public native final double getDouble(long offset);
public native final String getString(long offset);
+ /**
+ The copyTo... methods copy the blob's data, starting from the given
+ byte offset, into the array. A total of "size" _elements_ are copied.
+ */
+ public native final void copyToBoolArray(long offset, boolean[] array, int size);
+ public native final void copyToInt8Array(long offset, byte[] array, int size);
+ public native final void copyToInt16Array(long offset, short[] array, int size);
+ public native final void copyToInt32Array(long offset, int[] array, int size);
+ public native final void copyToInt64Array(long offset, long[] array, int size);
+ public native final void copyToFloatArray(long offset, float[] array, int size);
+ public native final void copyToDoubleArray(long offset, double[] array, int size);
+
public native final void putBool(long offset, boolean x);
public native final void putInt8(long offset, byte x);
public native final void putInt16(long offset, short x);
@@ -52,6 +64,14 @@
public native final void putDouble(long offset, double x);
public native final void putString(long offset, String x);
+ public native final void putBoolArray(long offset, boolean[] x);
+ public native final void putInt8Array(long offset, byte[] x);
+ public native final void putInt16Array(long offset, short[] x);
+ public native final void putInt32Array(long offset, int[] x);
+ public native final void putInt64Array(long offset, long[] x);
+ public native final void putFloatArray(long offset, float[] x);
+ public native final void putDoubleArray(long offset, double[] x);
+
public native final void putBlob(long offset, HwBlob blob);
public native final long handle();
diff --git a/core/java/android/os/HwRemoteBinder.java b/core/java/android/os/HwRemoteBinder.java
index 2f89ce6..a07e42c 100644
--- a/core/java/android/os/HwRemoteBinder.java
+++ b/core/java/android/os/HwRemoteBinder.java
@@ -63,4 +63,9 @@
}
private long mNativeContext;
+
+ @Override
+ public final native boolean equals(Object other);
+ @Override
+ public final native int hashCode();
}
diff --git a/core/java/android/os/IServiceManager.java b/core/java/android/os/IServiceManager.java
index 87c65ec..2176a78 100644
--- a/core/java/android/os/IServiceManager.java
+++ b/core/java/android/os/IServiceManager.java
@@ -45,13 +45,13 @@
* Place a new @a service called @a name into the service
* manager.
*/
- void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
- throws RemoteException;
+ void addService(String name, IBinder service, boolean allowIsolated, int dumpFlags)
+ throws RemoteException;
/**
* Return a list of all currently running services.
*/
- String[] listServices(int dumpPriority) throws RemoteException;
+ String[] listServices(int dumpFlags) throws RemoteException;
/**
* Assign a permission controller to the service manager. After set, this
@@ -72,9 +72,13 @@
/*
* Must update values in IServiceManager.h
*/
- int DUMP_PRIORITY_CRITICAL = 1 << 0;
- int DUMP_PRIORITY_HIGH = 1 << 1;
- int DUMP_PRIORITY_NORMAL = 1 << 2;
- int DUMP_PRIORITY_ALL = DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_HIGH
- | DUMP_PRIORITY_NORMAL;
+ /* Allows services to dump sections according to priorities. */
+ int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0;
+ int DUMP_FLAG_PRIORITY_HIGH = 1 << 1;
+ int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2;
+ int DUMP_FLAG_PRIORITY_ALL = DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH
+ | DUMP_FLAG_PRIORITY_NORMAL;
+ /* Allows services to dump sections in protobuf format. */
+ int DUMP_FLAG_PROTO = 1 << 3;
+
}
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/java/android/os/IStatsCallbacks.aidl
similarity index 64%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/java/android/os/IStatsCallbacks.aidl
index a987a16..02e7cd3 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/java/android/os/IStatsCallbacks.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2015 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
*
- * 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,
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package android.os;
-import android.graphics.Bitmap;
-
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
+/**
+ * Callback for Statsd to allow binder calls to clients.
+ * {@hide}
+ */
+interface IStatsCallbacks {
+ void onReceiveLogs(out byte[] log);
}
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index 20f6c8e..c0a95cc 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -46,10 +46,10 @@
* Uses AlarmManager.setRepeating API, so if the timestamp is in past, alarm fires immediately,
* and alarm is inexact.
*/
- oneway void setPollingAlarms(long timestampMs, long intervalMs);
+ oneway void setPullingAlarms(long timestampMs, long intervalMs);
/** Cancel any repeating polling alarm. */
- oneway void cancelPollingAlarms();
+ oneway void cancelPullingAlarms();
/** Pull the specified data. Results will be sent to statsd when complete. */
StatsLogEventWrapper[] pullData(int pullCode);
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index daacc4e..480296c 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -16,6 +16,8 @@
package android.os;
+import android.os.IStatsCallbacks;
+
/**
* Binder interface to communicate with the statistics management service.
* {@hide}
@@ -61,4 +63,15 @@
* Inform stats that an app was removed.
*/
oneway void informOnePackageRemoved(in String app, in int uid);
+
+ /**
+ * Trigger pushLog to force push stats log entries from statsd on client side.
+ */
+ void requestPush();
+
+ /**
+ * Listen to statsd to send stats log entries.
+ * TODO: Limit callbacks with specific configurations.
+ */
+ void subscribeStatsLog(IStatsCallbacks callbacks);
}
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 2dc3beb..ca9cbec 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -295,7 +295,11 @@
return STRING_EN_XA.equals(locale) || STRING_AR_XB.equals(locale);
}
- private static boolean isPseudoLocale(Locale locale) {
+ /**
+ * Returns true if locale is a pseudo-locale, false otherwise.
+ * {@hide}
+ */
+ public static boolean isPseudoLocale(Locale locale) {
return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale);
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 857e8a6..c2cf3967 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -561,6 +561,22 @@
mClassCookies = from.mClassCookies;
}
+ /** @hide */
+ public Map<Class, Object> copyClassCookies() {
+ return new ArrayMap<>(mClassCookies);
+ }
+
+ /** @hide */
+ public void putClassCookies(Map<Class, Object> cookies) {
+ if (cookies == null) {
+ return;
+ }
+ if (mClassCookies == null) {
+ mClassCookies = new ArrayMap<>();
+ }
+ mClassCookies.putAll(cookies);
+ }
+
/**
* Report whether the parcel contains any marshalled file descriptors.
*/
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index f41848f..42ec315 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -83,7 +83,7 @@
* @param service the service object
*/
public static void addService(String name, IBinder service) {
- addService(name, service, false, IServiceManager.DUMP_PRIORITY_NORMAL);
+ addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL);
}
/**
@@ -96,7 +96,7 @@
* to access this service
*/
public static void addService(String name, IBinder service, boolean allowIsolated) {
- addService(name, service, allowIsolated, IServiceManager.DUMP_PRIORITY_NORMAL);
+ addService(name, service, allowIsolated, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL);
}
/**
@@ -143,7 +143,7 @@
*/
public static String[] listServices() {
try {
- return getIServiceManager().listServices(IServiceManager.DUMP_PRIORITY_ALL);
+ return getIServiceManager().listServices(IServiceManager.DUMP_FLAG_PRIORITY_ALL);
} catch (RemoteException e) {
Log.e(TAG, "error in listServices", e);
return null;
diff --git a/core/java/android/os/ShellCallback.java b/core/java/android/os/ShellCallback.java
index e7fe697..ad9fbfb 100644
--- a/core/java/android/os/ShellCallback.java
+++ b/core/java/android/os/ShellCallback.java
@@ -35,8 +35,9 @@
IShellCallback mShellCallback;
class MyShellCallback extends IShellCallback.Stub {
- public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
- return onOpenOutputFile(path, seLinuxContext);
+ public ParcelFileDescriptor openFile(String path, String seLinuxContext,
+ String mode) {
+ return onOpenFile(path, seLinuxContext, mode);
}
}
@@ -48,23 +49,27 @@
}
/**
- * Ask the shell to open a file for writing. This will truncate the file if it
- * already exists. It will create the file if it doesn't exist.
+ * Ask the shell to open a file. If opening for writing, will truncate the file if it
+ * already exists and will create the file if it doesn't exist.
* @param path Path of the file to be opened/created.
* @param seLinuxContext Optional SELinux context that must be allowed to have
* access to the file; if null, nothing is required.
+ * @param mode Mode to open file in: "r" for input/reading an existing file,
+ * "r+" for reading/writing an existing file, "w" for output/writing a new file (either
+ * creating or truncating an existing one), "w+" for reading/writing a new file (either
+ * creating or truncating an existing one).
*/
- public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
- if (DEBUG) Log.d(TAG, "openOutputFile " + this + ": mLocal=" + mLocal
+ public ParcelFileDescriptor openFile(String path, String seLinuxContext, String mode) {
+ if (DEBUG) Log.d(TAG, "openFile " + this + " mode=" + mode + ": mLocal=" + mLocal
+ " mShellCallback=" + mShellCallback);
if (mLocal) {
- return onOpenOutputFile(path, seLinuxContext);
+ return onOpenFile(path, seLinuxContext, mode);
}
if (mShellCallback != null) {
try {
- return mShellCallback.openOutputFile(path, seLinuxContext);
+ return mShellCallback.openFile(path, seLinuxContext, mode);
} catch (RemoteException e) {
Log.w(TAG, "Failure opening " + path, e);
}
@@ -72,7 +77,7 @@
return null;
}
- public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext, String mode) {
return null;
}
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index e4a12e8..d75219f 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -17,6 +17,7 @@
package android.os;
import android.util.Slog;
+
import com.android.internal.util.FastPrintWriter;
import java.io.BufferedInputStream;
@@ -118,13 +119,33 @@
mErrPrintWriter.flush();
}
if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget);
- mResultReceiver.send(res, null);
+ if (mResultReceiver != null) {
+ mResultReceiver.send(res, null);
+ }
}
if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
return res;
}
/**
+ * Adopt the ResultReceiver that was given to this shell command from it, taking
+ * it over. Primarily used to dispatch to another shell command. Once called,
+ * this shell command will no longer return its own result when done.
+ */
+ public ResultReceiver adoptResultReceiver() {
+ ResultReceiver rr = mResultReceiver;
+ mResultReceiver = null;
+ return rr;
+ }
+
+ /**
+ * Return the raw FileDescriptor for the output stream.
+ */
+ public FileDescriptor getOutFileDescriptor() {
+ return mOut;
+ }
+
+ /**
* Return direct raw access (not buffered) to the command's output data stream.
*/
public OutputStream getRawOutputStream() {
@@ -145,6 +166,13 @@
}
/**
+ * Return the raw FileDescriptor for the error stream.
+ */
+ public FileDescriptor getErrFileDescriptor() {
+ return mErr;
+ }
+
+ /**
* Return direct raw access (not buffered) to the command's error output data stream.
*/
public OutputStream getRawErrorStream() {
@@ -168,6 +196,13 @@
}
/**
+ * Return the raw FileDescriptor for the input stream.
+ */
+ public FileDescriptor getInFileDescriptor() {
+ return mIn;
+ }
+
+ /**
* Return direct raw access (not buffered) to the command's input data stream.
*/
public InputStream getRawInputStream() {
@@ -191,10 +226,10 @@
* Helper for just system services to ask the shell to open an output file.
* @hide
*/
- public ParcelFileDescriptor openOutputFileForSystem(String path) {
+ public ParcelFileDescriptor openFileForSystem(String path, String mode) {
try {
- ParcelFileDescriptor pfd = getShellCallback().openOutputFile(path,
- "u:r:system_server:s0");
+ ParcelFileDescriptor pfd = getShellCallback().openFile(path,
+ "u:r:system_server:s0", mode);
if (pfd != null) {
return pfd;
}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 826ec1eb..6ce12c1 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -20,7 +20,6 @@
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
-import android.app.ApplicationErrorReport;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -35,6 +34,7 @@
import android.util.Slog;
import android.view.IWindowManager;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.RuntimeInit;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.HexDump;
@@ -48,8 +48,10 @@
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Deque;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -352,8 +354,8 @@
} else {
msg = "StrictMode policy violation:";
}
- if (info.crashInfo != null) {
- Log.d(TAG, msg + " " + info.crashInfo.stackTrace);
+ if (info.hasStackTrace()) {
+ Log.d(TAG, msg + " " + info.getStackTrace());
} else {
Log.d(TAG, msg + " missing stack trace!");
}
@@ -1247,28 +1249,6 @@
}
}
- /** Like parsePolicyFromMessage(), but returns the violation. */
- private static int parseViolationFromMessage(String message) {
- if (message == null) {
- return 0;
- }
- int violationIndex = message.indexOf("violation=");
- if (violationIndex == -1) {
- return 0;
- }
- int numberStartIndex = violationIndex + "violation=".length();
- int numberEndIndex = message.indexOf(' ', numberStartIndex);
- if (numberEndIndex == -1) {
- numberEndIndex = message.length();
- }
- String violationString = message.substring(numberStartIndex, numberEndIndex);
- try {
- return Integer.parseInt(violationString);
- } catch (NumberFormatException e) {
- return 0;
- }
- }
-
private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
new ThreadLocal<ArrayList<ViolationInfo>>() {
@Override
@@ -1516,7 +1496,7 @@
// to people who push/pop temporary policy in regions of code,
// hence the policy being passed around.
void handleViolation(final ViolationInfo info) {
- if (info == null || info.crashInfo == null || info.crashInfo.stackTrace == null) {
+ if (info == null || !info.hasStackTrace()) {
Log.wtf(TAG, "unexpected null stacktrace");
return;
}
@@ -1530,7 +1510,7 @@
gatheredViolations.set(violations);
}
for (ViolationInfo previous : violations) {
- if (info.crashInfo.stackTrace.equals(previous.crashInfo.stackTrace)) {
+ if (info.getStackTrace().equals(previous.getStackTrace())) {
// Duplicate. Don't log.
return;
}
@@ -1576,8 +1556,7 @@
}
if (violationMaskSubset != 0) {
- int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
- violationMaskSubset |= violationBit;
+ violationMaskSubset |= info.getViolationBit();
final int savedPolicyMask = getThreadPolicyMask();
final boolean justDropBox = (info.policy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
@@ -1622,8 +1601,7 @@
}
private static void executeDeathPenalty(ViolationInfo info) {
- int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
- throw new StrictModeViolation(info.policy, violationBit, null);
+ throw new StrictModeViolation(info.policy, info.getViolationBit(), null);
}
/**
@@ -1670,7 +1648,7 @@
private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
public void report(String message, Throwable allocationSite) {
- onVmPolicyViolation(message, allocationSite);
+ onVmPolicyViolation(allocationSite);
}
}
@@ -1709,7 +1687,7 @@
long instances = instanceCounts[i];
if (instances > limit) {
Throwable tr = new InstanceCountViolation(klass, instances, limit);
- onVmPolicyViolation(tr.getMessage(), tr);
+ onVmPolicyViolation(tr);
}
}
}
@@ -1833,22 +1811,24 @@
/** @hide */
public static void onSqliteObjectLeaked(String message, Throwable originStack) {
- onVmPolicyViolation(message, originStack);
+ Throwable t = new Throwable(message);
+ t.setStackTrace(originStack.getStackTrace());
+ onVmPolicyViolation(t);
}
/** @hide */
public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) {
- onVmPolicyViolation(null, originStack);
+ onVmPolicyViolation(originStack);
}
/** @hide */
public static void onIntentReceiverLeaked(Throwable originStack) {
- onVmPolicyViolation(null, originStack);
+ onVmPolicyViolation(originStack);
}
/** @hide */
public static void onServiceConnectionLeaked(Throwable originStack) {
- onVmPolicyViolation(null, originStack);
+ onVmPolicyViolation(originStack);
}
/** @hide */
@@ -1857,7 +1837,7 @@
if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) {
throw new FileUriExposedException(message);
} else {
- onVmPolicyViolation(null, new Throwable(message));
+ onVmPolicyViolation(new Throwable(message));
}
}
@@ -1869,7 +1849,7 @@
+ location
+ " without permission grant flags; did you forget"
+ " FLAG_GRANT_READ_URI_PERMISSION?";
- onVmPolicyViolation(null, new Throwable(message));
+ onVmPolicyViolation(new Throwable(message));
}
/** @hide */
@@ -1899,10 +1879,9 @@
} catch (UnknownHostException ignored) {
}
}
-
+ msg += HexDump.dumpHexString(firstPacket).trim() + " ";
final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0;
- onVmPolicyViolation(
- HexDump.dumpHexString(firstPacket).trim(), new Throwable(msg), forceDeath);
+ onVmPolicyViolation(new Throwable(msg), forceDeath);
}
/** @hide */
@@ -1912,24 +1891,23 @@
/** @hide */
public static void onUntaggedSocket() {
- onVmPolicyViolation(null, new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
+ onVmPolicyViolation(new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
}
// Map from VM violation fingerprint to uptime millis.
private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>();
/** @hide */
- public static void onVmPolicyViolation(String message, Throwable originStack) {
- onVmPolicyViolation(message, originStack, false);
+ public static void onVmPolicyViolation(Throwable originStack) {
+ onVmPolicyViolation(originStack, false);
}
/** @hide */
- public static void onVmPolicyViolation(
- String message, Throwable originStack, boolean forceDeath) {
+ public static void onVmPolicyViolation(Throwable originStack, boolean forceDeath) {
final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0;
final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath;
final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0;
- final ViolationInfo info = new ViolationInfo(message, originStack, sVmPolicy.mask);
+ final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask);
// Erase stuff not relevant for process-wide violations
info.numAnimationsRunning = 0;
@@ -2027,21 +2005,14 @@
* read back all the encoded violations.
*/
/* package */ static void readAndHandleBinderCallViolations(Parcel p) {
- // Our own stack trace to append
- StringWriter sw = new StringWriter();
- sw.append("# via Binder call with stack:\n");
- PrintWriter pw = new FastPrintWriter(sw, false, 256);
- new LogStackTrace().printStackTrace(pw);
- pw.flush();
- String ourStack = sw.toString();
-
+ LogStackTrace localCallSite = new LogStackTrace();
final int policyMask = getThreadPolicyMask();
final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
final int size = p.readInt();
for (int i = 0; i < size; i++) {
final ViolationInfo info = new ViolationInfo(p, !currentlyGathering);
- info.crashInfo.appendStackTrace(ourStack);
+ info.addLocalStack(localCallSite);
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
if (policy instanceof AndroidBlockGuardPolicy) {
((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info);
@@ -2254,7 +2225,7 @@
// StrictMode not enabled.
return;
}
- ((AndroidBlockGuardPolicy) policy).onUnbufferedIO();
+ policy.onUnbufferedIO();
}
/** @hide */
@@ -2264,7 +2235,7 @@
// StrictMode not enabled.
return;
}
- ((AndroidBlockGuardPolicy) policy).onReadFromDisk();
+ policy.onReadFromDisk();
}
/** @hide */
@@ -2274,12 +2245,11 @@
// StrictMode not enabled.
return;
}
- ((AndroidBlockGuardPolicy) policy).onWriteToDisk();
+ policy.onWriteToDisk();
}
- // Guarded by StrictMode.class
- private static final HashMap<Class, Integer> sExpectedActivityInstanceCount =
- new HashMap<Class, Integer>();
+ @GuardedBy("StrictMode.class")
+ private static final HashMap<Class, Integer> sExpectedActivityInstanceCount = new HashMap<>();
/**
* Returns an object that is used to track instances of activites. The activity should store a
@@ -2354,7 +2324,7 @@
long instances = VMDebug.countInstancesOfClass(klass, false);
if (instances > limit) {
Throwable tr = new InstanceCountViolation(klass, instances, limit);
- onVmPolicyViolation(tr.getMessage(), tr);
+ onVmPolicyViolation(tr);
}
}
@@ -2366,10 +2336,16 @@
*/
@TestApi
public static final class ViolationInfo implements Parcelable {
- public final String message;
+ /** Stack and violation details. */
+ @Nullable private final Throwable mThrowable;
- /** Stack and other stuff info. */
- @Nullable public final ApplicationErrorReport.CrashInfo crashInfo;
+ /** Path leading to a violation that occurred across binder. */
+ private final Deque<StackTraceElement[]> mBinderStack = new ArrayDeque<>();
+
+ /** Memoized stack trace of full violation. */
+ @Nullable private String mStackTrace;
+ /** Memoized violation bit. */
+ private int mViolationBit;
/** The strict mode policy mask at the time of violation. */
public final int policy;
@@ -2404,19 +2380,13 @@
/** Create an uninitialized instance of ViolationInfo */
public ViolationInfo() {
- message = null;
- crashInfo = null;
+ mThrowable = null;
policy = 0;
}
- public ViolationInfo(Throwable tr, int policy) {
- this(null, tr, policy);
- }
-
/** Create an instance of ViolationInfo initialized from an exception. */
- public ViolationInfo(String message, Throwable tr, int policy) {
- this.message = message;
- crashInfo = new ApplicationErrorReport.CrashInfo(tr);
+ public ViolationInfo(Throwable tr, int policy) {
+ this.mThrowable = tr;
violationUptimeMillis = SystemClock.uptimeMillis();
this.policy = policy;
this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount();
@@ -2446,11 +2416,96 @@
}
}
+ /** Equivalent output to {@link ApplicationErrorReport.CrashInfo#stackTrace}. */
+ public String getStackTrace() {
+ if (mThrowable != null && mStackTrace == null) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new FastPrintWriter(sw, false, 256);
+ mThrowable.printStackTrace(pw);
+ for (StackTraceElement[] traces : mBinderStack) {
+ pw.append("# via Binder call with stack:\n");
+ for (StackTraceElement traceElement : traces) {
+ pw.append("\tat ");
+ pw.append(traceElement.toString());
+ pw.append('\n');
+ }
+ }
+ pw.flush();
+ pw.close();
+ mStackTrace = sw.toString();
+ }
+ return mStackTrace;
+ }
+
+ /**
+ * Optional message describing this violation.
+ *
+ * @hide
+ */
+ @TestApi
+ public String getViolationDetails() {
+ if (mThrowable != null) {
+ return mThrowable.getMessage();
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * If this violation has a useful stack trace.
+ *
+ * @hide
+ */
+ public boolean hasStackTrace() {
+ return mThrowable != null;
+ }
+
+ /**
+ * Add a {@link Throwable} from the current process that caused the underlying violation. We
+ * only preserve the stack trace elements.
+ *
+ * @hide
+ */
+ void addLocalStack(Throwable t) {
+ mBinderStack.addFirst(t.getStackTrace());
+ }
+
+ /**
+ * Retrieve the type of StrictMode violation.
+ *
+ * @hide
+ */
+ int getViolationBit() {
+ if (mThrowable == null || mThrowable.getMessage() == null) {
+ return 0;
+ }
+ if (mViolationBit != 0) {
+ return mViolationBit;
+ }
+ String message = mThrowable.getMessage();
+ int violationIndex = message.indexOf("violation=");
+ if (violationIndex == -1) {
+ return 0;
+ }
+ int numberStartIndex = violationIndex + "violation=".length();
+ int numberEndIndex = message.indexOf(' ', numberStartIndex);
+ if (numberEndIndex == -1) {
+ numberEndIndex = message.length();
+ }
+ String violationString = message.substring(numberStartIndex, numberEndIndex);
+ try {
+ mViolationBit = Integer.parseInt(violationString);
+ return mViolationBit;
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
@Override
public int hashCode() {
int result = 17;
- if (crashInfo != null) {
- result = 37 * result + crashInfo.stackTrace.hashCode();
+ if (mThrowable != null) {
+ result = 37 * result + mThrowable.hashCode();
}
if (numAnimationsRunning != 0) {
result *= 37;
@@ -2478,11 +2533,20 @@
* should be removed.
*/
public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
- message = in.readString();
- if (in.readInt() != 0) {
- crashInfo = new ApplicationErrorReport.CrashInfo(in);
- } else {
- crashInfo = null;
+ mThrowable = (Throwable) in.readSerializable();
+ int binderStackSize = in.readInt();
+ for (int i = 0; i < binderStackSize; i++) {
+ StackTraceElement[] traceElements = new StackTraceElement[in.readInt()];
+ for (int j = 0; j < traceElements.length; j++) {
+ StackTraceElement element =
+ new StackTraceElement(
+ in.readString(),
+ in.readString(),
+ in.readString(),
+ in.readInt());
+ traceElements[j] = element;
+ }
+ mBinderStack.add(traceElements);
}
int rawPolicy = in.readInt();
if (unsetGatheringBit) {
@@ -2502,12 +2566,16 @@
/** Save a ViolationInfo instance to a parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(message);
- if (crashInfo != null) {
- dest.writeInt(1);
- crashInfo.writeToParcel(dest, flags);
- } else {
- dest.writeInt(0);
+ dest.writeSerializable(mThrowable);
+ dest.writeInt(mBinderStack.size());
+ for (StackTraceElement[] traceElements : mBinderStack) {
+ dest.writeInt(traceElements.length);
+ for (StackTraceElement element : traceElements) {
+ dest.writeString(element.getClassName());
+ dest.writeString(element.getMethodName());
+ dest.writeString(element.getFileName());
+ dest.writeInt(element.getLineNumber());
+ }
}
int start = dest.dataPosition();
dest.writeInt(policy);
@@ -2542,8 +2610,8 @@
/** Dump a ViolationInfo instance to a Printer. */
public void dump(Printer pw, String prefix) {
- if (crashInfo != null) {
- crashInfo.dump(pw, prefix);
+ if (mThrowable != null) {
+ pw.println(prefix + "stackTrace: " + getStackTrace());
}
pw.println(prefix + "policy: " + policy);
if (durationMillis != -1) {
diff --git a/core/java/android/os/TokenWatcher.java b/core/java/android/os/TokenWatcher.java
index 9b3a2d6..00333dad 100644
--- a/core/java/android/os/TokenWatcher.java
+++ b/core/java/android/os/TokenWatcher.java
@@ -16,17 +16,23 @@
package android.os;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.WeakHashMap;
-import java.util.Set;
import android.util.Log;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.WeakHashMap;
+
/**
- * Helper class that helps you use IBinder objects as reference counted
- * tokens. IBinders make good tokens because we find out when they are
- * removed
+ * A TokenWatcher watches a collection of {@link IBinder}s. IBinders are added
+ * to the collection by calling {@link #acquire}, and removed by calling {@link
+ * #release}. IBinders are also implicitly removed when they become weakly
+ * reachable. Each IBinder may be added at most once.
*
+ * The {@link #acquired} method is invoked by posting to the specified handler
+ * whenever the size of the watched collection becomes nonzero. The {@link
+ * #released} method is invoked on the specified handler whenever the size of
+ * the watched collection becomes zero.
*/
public abstract class TokenWatcher
{
@@ -59,15 +65,23 @@
* Record that this token has been acquired. When acquire is called, and
* the current count is 0, the acquired method is called on the given
* handler.
- *
- * @param token An IBinder object. If this token has already been acquired,
- * no action is taken.
+ *
+ * Note that the same {@code token} can only be acquired once. If this
+ * {@code token} has already been acquired, no action is taken. The first
+ * subsequent call to {@link #release} will release this {@code token}
+ * immediately.
+ *
+ * @param token An IBinder object.
* @param tag A string used by the {@link #dump} method for debugging,
* to see who has references.
*/
public void acquire(IBinder token, String tag)
{
synchronized (mTokens) {
+ if (mTokens.containsKey(token)) {
+ return;
+ }
+
// explicitly checked to avoid bogus sendNotification calls because
// of the WeakHashMap and the GC
int oldSize = mTokens.size();
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 8c68871..c54b72d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -319,6 +319,23 @@
public static final String DISALLOW_CONFIG_VPN = "no_config_vpn";
/**
+ * Specifies if date, time and timezone configuring is disallowed.
+ *
+ * <p>When restriction is set by device owners, it applies globally - i.e., it disables date,
+ * time and timezone setting on the entire device and all users will be affected. When it's set
+ * by profile owners, it's only applied to the managed user.
+ * <p>The default value is <code>false</code>.
+ *
+ * <p>This user restriction has no effect on managed profiles.
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+
+ /**
* Specifies if a user is disallowed from configuring Tethering
* & portable hotspots. This can only be set by device owners and profile owners on the
* primary user. The default value is <code>false</code>.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c200ae7..2d6a7b0 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -72,6 +72,7 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.MemoryIntArray;
+import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
@@ -1886,7 +1887,11 @@
arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
IContentProvider cp = mProviderHolder.getProvider(cr);
+ String prevValue = getStringForUser(cr, name, userHandle);
cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
+ String newValue = getStringForUser(cr, name, userHandle);
+ StatsLog.write(StatsLog.SETTING_CHANGED, name, value, newValue, prevValue, tag,
+ makeDefault ? 1 : 0, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
return false;
@@ -3096,6 +3101,12 @@
private static final Validator DIM_SCREEN_VALIDATOR = sBooleanValidator;
/**
+ * The display color mode.
+ * @hide
+ */
+ public static final String DISPLAY_COLOR_MODE = "display_color_mode";
+
+ /**
* The amount of time in milliseconds before the device goes to sleep or begins
* to dream after a period of inactivity. This value is also known as the
* user activity timeout period since the screen isn't necessarily turned off
@@ -7615,6 +7626,13 @@
public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
/**
+ * An integer representing the Bluetooth Class of Device (CoD).
+ *
+ * @hide
+ */
+ public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device";
+
+ /**
* A Long representing a bitmap of profiles that should be disabled when bluetooth starts.
* See {@link android.bluetooth.BluetoothProfile}.
* {@hide}
@@ -9649,7 +9667,7 @@
* Get the key that retrieves a bluetooth Input Device's priority.
* @hide
*/
- public static final String getBluetoothInputDevicePriorityKey(String address) {
+ public static final String getBluetoothHidHostPriorityKey(String address) {
return BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
}
diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java
index 812c956..0c4eeda 100644
--- a/core/java/android/security/NetworkSecurityPolicy.java
+++ b/core/java/android/security/NetworkSecurityPolicy.java
@@ -16,7 +16,6 @@
package android.security;
-import android.annotation.TestApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.security.net.config.ApplicationConfig;
@@ -63,7 +62,8 @@
* traffic from applications is handled by higher-level network stacks/components which can
* honor this aspect of the policy.
*
- * <p>NOTE: {@link android.webkit.WebView} does not honor this flag.
+ * <p>NOTE: {@link android.webkit.WebView} honors this flag for applications targeting API level
+ * 26 and up.
*/
public boolean isCleartextTrafficPermitted() {
return libcore.net.NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index b9e5505..52f48ef 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -164,7 +164,8 @@
* <p>
* The default configuration has the following properties:
* <ol>
- * <li>Cleartext traffic is permitted for non-ephemeral apps.</li>
+ * <li>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic
+ * is allowed by default.</li>
* <li>Cleartext traffic is not permitted for ephemeral apps.</li>
* <li>HSTS is not enforced.</li>
* <li>No certificate pinning is used.</li>
@@ -183,7 +184,8 @@
// System certificate store, does not bypass static pins.
.addCertificatesEntryRef(
new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
- final boolean cleartextTrafficPermitted = info.targetSandboxVersion < 2;
+ final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P
+ && info.targetSandboxVersion < 2;
builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
// Applications targeting N and above must opt in into trusting the user added certificate
// store.
diff --git a/core/java/android/service/autofill/CustomDescription.java b/core/java/android/service/autofill/CustomDescription.java
index fd30857..b8e8b19 100644
--- a/core/java/android/service/autofill/CustomDescription.java
+++ b/core/java/android/service/autofill/CustomDescription.java
@@ -32,7 +32,7 @@
import java.util.ArrayList;
/**
- * Defines a custom description for the Save UI affordance.
+ * Defines a custom description for the autofill save UI.
*
* <p>This is useful when the autofill service needs to show a detailed view of what would be saved;
* for example, when the screen contains a credit card, it could display a logo of the credit card
@@ -131,7 +131,7 @@
* <p><b>Note:</b> If any child view of presentation triggers a
* {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent) pending intent
* on click}, such {@link PendingIntent} must follow the restrictions below, otherwise
- * it might not be triggered or the Save affordance might not be shown when its activity
+ * it might not be triggered or the autofill save UI might not be shown when its activity
* is finished:
* <ul>
* <li>It cannot be created with the {@link PendingIntent#FLAG_IMMUTABLE} flag.
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index d2033fa..2f6342a 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -31,6 +31,8 @@
import android.view.autofill.AutofillId;
import android.widget.RemoteViews;
+import com.android.internal.util.Preconditions;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -51,9 +53,16 @@
*/
public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1;
+ /**
+ * Used in conjunction to {@link FillResponse.Builder#disableAutofill(long)} to disable autofill
+ * only for the activiy associated with the {@link FillResponse}, instead of the whole app.
+ */
+ public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2;
+
/** @hide */
@IntDef(flag = true, value = {
- FLAG_TRACK_CONTEXT_COMMITED
+ FLAG_TRACK_CONTEXT_COMMITED,
+ FLAG_DISABLE_ACTIVITY_ONLY
})
@Retention(RetentionPolicy.SOURCE)
@interface FillResponseFlags {}
@@ -65,6 +74,7 @@
private final @Nullable IntentSender mAuthentication;
private final @Nullable AutofillId[] mAuthenticationIds;
private final @Nullable AutofillId[] mIgnoredIds;
+ private final long mDisableDuration;
private final int mFlags;
private int mRequestId;
@@ -76,6 +86,7 @@
mAuthentication = builder.mAuthentication;
mAuthenticationIds = builder.mAuthenticationIds;
mIgnoredIds = builder.mIgnoredIds;
+ mDisableDuration = builder.mDisableDuration;
mFlags = builder.mFlags;
mRequestId = INVALID_REQUEST_ID;
}
@@ -116,6 +127,11 @@
}
/** @hide */
+ public long getDisableDuration() {
+ return mDisableDuration;
+ }
+
+ /** @hide */
public int getFlags() {
return mFlags;
}
@@ -150,6 +166,7 @@
private IntentSender mAuthentication;
private AutofillId[] mAuthenticationIds;
private AutofillId[] mIgnoredIds;
+ private long mDisableDuration;
private int mFlags;
private boolean mDestroyed;
@@ -187,23 +204,25 @@
* which is used to visualize visualize the response for triggering the authentication
* flow.
*
- * <p></><strong>Note:</strong> Do not make the provided pending intent
+ * <p><b>Note:</b> Do not make the provided pending intent
* immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
* platform needs to fill in the authentication arguments.
*
* @param authentication Intent to an activity with your authentication flow.
* @param presentation The presentation to visualize the response.
- * @param ids id of Views that when focused will display the authentication UI affordance.
+ * @param ids id of Views that when focused will display the authentication UI.
*
* @return This builder.
* @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if
- * neither {@code authentication} nor {@code presentation} is non-{@code null}.
+ * both {@code authentication} and {@code presentation} are {@code null}, or if
+ * both {@code authentication} and {@code presentation} are non-{@code null}
*
* @see android.app.PendingIntent#getIntentSender()
*/
public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids,
@Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
throwIfDestroyed();
+ throwIfDisableAutofillCalled();
if (ids == null || ids.length == 0) {
throw new IllegalArgumentException("ids cannot be null or empry");
}
@@ -226,6 +245,7 @@
* text field representing the result of a Captcha challenge.
*/
public Builder setIgnoredIds(AutofillId...ids) {
+ throwIfDestroyed();
mIgnoredIds = ids;
return this;
}
@@ -246,6 +266,7 @@
*/
public @NonNull Builder addDataset(@Nullable Dataset dataset) {
throwIfDestroyed();
+ throwIfDisableAutofillCalled();
if (dataset == null) {
return this;
}
@@ -265,6 +286,7 @@
*/
public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) {
throwIfDestroyed();
+ throwIfDisableAutofillCalled();
mSaveInfo = saveInfo;
return this;
}
@@ -295,30 +317,82 @@
/**
* Sets flags changing the response behavior.
*
- * @param flags {@link #FLAG_TRACK_CONTEXT_COMMITED}, or {@code 0}.
+ * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and
+ * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}.
*
* @return This builder.
*/
public Builder setFlags(@FillResponseFlags int flags) {
throwIfDestroyed();
- mFlags = flags;
+ mFlags = Preconditions.checkFlagsArgument(flags,
+ FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+ return this;
+ }
+
+ /**
+ * Disables autofill for the app or activity.
+ *
+ * <p>This method is useful to optimize performance in cases where the service knows it
+ * can not autofill an app—for example, when the service has a list of "blacklisted"
+ * apps such as office suites.
+ *
+ * <p>By default, it disables autofill for all activities in the app, unless the response is
+ * {@link #setFlags(int) flagged} with {@link #FLAG_DISABLE_ACTIVITY_ONLY}.
+ *
+ * <p>Autofill for the app or activity is automatically re-enabled after any of the
+ * following conditions:
+ *
+ * <ol>
+ * <li>{@code duration} milliseconds have passed.
+ * <li>The autofill service for the user has changed.
+ * <li>The device has rebooted.
+ * </ol>
+ *
+ * <p><b>Note:</b> Activities that are running when autofill is re-enabled remain
+ * disabled for autofill until they finish and restart.
+ *
+ * @param duration duration to disable autofill, in milliseconds.
+ *
+ * @return this builder
+ *
+ * @throws IllegalArgumentException if {@code duration} is not a positive number.
+ * @throws IllegalStateException if either {@link #addDataset(Dataset)},
+ * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, or
+ * {@link #setSaveInfo(SaveInfo)} was already called.
+ */
+ public Builder disableAutofill(long duration) {
+ throwIfDestroyed();
+ if (duration <= 0) {
+ throw new IllegalArgumentException("duration must be greater than 0");
+ }
+ if (mAuthentication != null || mDatasets != null || mSaveInfo != null) {
+ throw new IllegalStateException("disableAutofill() must be the only method called");
+ }
+
+ mDisableDuration = duration;
return this;
}
/**
* Builds a new {@link FillResponse} instance.
*
- * <p>You must provide at least one dataset or some savable ids or an authentication with a
- * presentation view.
+ * @throws IllegalStateException if any of the following conditions occur:
+ * <ol>
+ * <li>{@link #build()} was already called.
+ * <li>No call was made to {@link #addDataset(Dataset)},
+ * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+ * {@link #setSaveInfo(SaveInfo)}, or {@link #disableAutofill(long)}.
+ * </ol>
*
* @return A built response.
*/
public FillResponse build() {
throwIfDestroyed();
- if (mAuthentication == null && mDatasets == null && mSaveInfo == null) {
- throw new IllegalArgumentException("need to provide at least one DataSet or a "
- + "SaveInfo or an authentication with a presentation");
+ if (mAuthentication == null && mDatasets == null && mSaveInfo == null
+ && mDisableDuration == 0) {
+ throw new IllegalStateException("need to provide at least one DataSet or a "
+ + "SaveInfo or an authentication with a presentation or disable autofill");
}
mDestroyed = true;
return new FillResponse(this);
@@ -329,6 +403,12 @@
throw new IllegalStateException("Already called #build()");
}
}
+
+ private void throwIfDisableAutofillCalled() {
+ if (mDisableDuration > 0) {
+ throw new IllegalStateException("Already called #disableAutofill()");
+ }
+ }
}
/////////////////////////////////////
@@ -348,6 +428,7 @@
.append(", hasAuthentication=").append(mAuthentication != null)
.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds))
.append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
+ .append(", disableDuration=").append(mDisableDuration)
.append(", flags=").append(mFlags)
.append("]")
.toString();
@@ -371,6 +452,7 @@
parcel.writeParcelable(mAuthentication, flags);
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelableArray(mIgnoredIds, flags);
+ parcel.writeLong(mDisableDuration);
parcel.writeInt(mFlags);
parcel.writeInt(mRequestId);
}
@@ -402,6 +484,10 @@
}
builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
+ final long disableDuration = parcel.readLong();
+ if (disableDuration > 0) {
+ builder.disableAutofill(disableDuration);
+ }
builder.setFlags(parcel.readInt());
final FillResponse response = builder.build();
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index fde2416..9a1dcbb 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -535,14 +535,15 @@
* 16 digits, or 15 digits starting with 108:
*
* <pre class="prettyprint">
- * import android.service.autofill.Validators;
+ * import static android.service.autofill.Validators.and;
+ * import static android.service.autofill.Validators.or;
*
* Validator validator =
* and(
* new LuhnChecksumValidator(ccNumberId),
* or(
- * new RegexValidator(ccNumberId, Pattern.compile(""^\\d{16}$")),
- * new RegexValidator(ccNumberId, Pattern.compile(""^108\\d{12}$"))
+ * new RegexValidator(ccNumberId, Pattern.compile("^\\d{16}$")),
+ * new RegexValidator(ccNumberId, Pattern.compile("^108\\d{12}$"))
* )
* );
* </pre>
@@ -562,14 +563,14 @@
* 4 digits on each field:
*
* <pre class="prettyprint">
- * import android.service.autofill.Validators;
+ * import static android.service.autofill.Validators.and;
*
* Validator validator =
* and(
- * new RegexValidator(ccNumberId1, Pattern.compile(""^\\d{4}$")),
- * new RegexValidator(ccNumberId2, Pattern.compile(""^\\d{4}$")),
- * new RegexValidator(ccNumberId3, Pattern.compile(""^\\d{4}$")),
- * new RegexValidator(ccNumberId4, Pattern.compile(""^\\d{4}$"))
+ * new RegexValidator(ccNumberId1, Pattern.compile("^\\d{4}$")),
+ * new RegexValidator(ccNumberId2, Pattern.compile("^\\d{4}$")),
+ * new RegexValidator(ccNumberId3, Pattern.compile("^\\d{4}$")),
+ * new RegexValidator(ccNumberId4, Pattern.compile("^\\d{4}$"))
* );
* </pre>
*
diff --git a/core/java/android/service/autofill/Transformation.java b/core/java/android/service/autofill/Transformation.java
index 4cef261..aa8bc9b 100644
--- a/core/java/android/service/autofill/Transformation.java
+++ b/core/java/android/service/autofill/Transformation.java
@@ -19,7 +19,7 @@
* Helper class used to change a child view of a {@link android.widget.RemoteViews presentation
* template} at runtime, using the values of fields contained in the screen.
*
- * <p>Typically used by {@link CustomDescription} to provide a customized Save UI affordance.
+ * <p>Typically used by {@link CustomDescription} to provide a customized autofill save UI.
*/
public interface Transformation {
}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 625dd9e..cd177c4 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -16,6 +16,8 @@
package android.service.voice;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
import android.annotation.Nullable;
import android.app.Activity;
import android.app.Dialog;
@@ -46,7 +48,6 @@
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -63,8 +64,6 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
/**
* An active voice interaction session, providing a facility for the implementation
* to interact with the user in the voice interaction layer. The user interface is
@@ -110,16 +109,6 @@
*/
public static final int SHOW_SOURCE_ACTIVITY = 1<<4;
- // Keys for Bundle values
- /** @hide */
- public static final String KEY_DATA = "data";
- /** @hide */
- public static final String KEY_STRUCTURE = "structure";
- /** @hide */
- public static final String KEY_CONTENT = "content";
- /** @hide */
- public static final String KEY_RECEIVER_EXTRAS = "receiverExtras";
-
final Context mContext;
final HandlerCaller mHandlerCaller;
@@ -1423,9 +1412,7 @@
public void setContentView(View view) {
ensureWindowCreated();
mContentFrame.removeAllViews();
- mContentFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
+ mContentFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentFrame.requestApplyInsets();
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index ac5c2e9..4d2a962 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1910,7 +1910,7 @@
MeasuredText mt = MeasuredText.obtain();
TextLine tl = TextLine.obtain();
try {
- mt.setPara(text, start, end, textDir, null);
+ mt.setPara(text, start, end, textDir);
Directions directions;
int dir;
if (mt.mEasy) {
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index ffc44a7..3d9fba7 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -39,7 +39,6 @@
private int mPos;
private TextPaint mWorkPaint;
- private StaticLayout.Builder mBuilder;
private MeasuredText() {
mWorkPaint = new TextPaint();
@@ -82,7 +81,6 @@
void finish() {
mText = null;
- mBuilder = null;
if (mLen > 1000) {
mWidths = null;
mChars = null;
@@ -93,9 +91,7 @@
/**
* Analyzes text for bidirectional runs. Allocates working buffers.
*/
- void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
- StaticLayout.Builder builder) {
- mBuilder = builder;
+ void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
mText = text;
mTextStart = start;
@@ -159,12 +155,12 @@
/**
* Apply the style.
*
- * If StaticLyaout.Builder is not provided in setPara() method, this method measures the styled
- * text width.
- * If StaticLayout.Builder is provided in setPara() method, this method just passes the style
- * information to native code by calling StaticLayout.Builder.addstyleRun() and returns 0.
+ * If nativeStaticLayoutPtr is 0, this method measures the styled text width.
+ * If nativeStaticLayoutPtr is not 0, this method just passes the style information to native
+ * code by calling StaticLayout.addstyleRun() and returns 0.
*/
- float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+ float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm,
+ long nativeStaticLayoutPtr) {
if (fm != null) {
paint.getFontMetricsInt(fm);
}
@@ -174,10 +170,10 @@
if (mEasy) {
final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
- if (mBuilder == null) {
+ if (nativeStaticLayoutPtr == 0) {
return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
} else {
- mBuilder.addStyleRun(paint, p, p + len, isRtl);
+ StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, p, p + len, isRtl);
return 0.0f; // Builder.addStyleRun doesn't return the width.
}
}
@@ -187,12 +183,12 @@
for (int q = p, i = p + 1, e = p + len;; ++i) {
if (i == e || mLevels[i] != level) {
final boolean isRtl = (level & 0x1) != 0;
- if (mBuilder == null) {
+ if (nativeStaticLayoutPtr == 0) {
totalAdvance +=
paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
} else {
// Builder.addStyleRun doesn't return the width.
- mBuilder.addStyleRun(paint, q, i, isRtl);
+ StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, q, i, isRtl);
}
if (i == e) {
break;
@@ -201,11 +197,15 @@
level = mLevels[i];
}
}
- return totalAdvance; // If mBuilder is null, the result is zero.
+ return totalAdvance; // If nativeStaticLayoutPtr is 0, the result is zero.
+ }
+
+ float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+ return addStyleRun(paint, len, fm, 0 /* native ptr */);
}
float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
- Paint.FontMetricsInt fm) {
+ Paint.FontMetricsInt fm, long nativeStaticLayoutPtr) {
TextPaint workPaint = mWorkPaint;
workPaint.set(paint);
@@ -224,18 +224,18 @@
float wid;
if (replacement == null) {
- wid = addStyleRun(workPaint, len, fm);
+ wid = addStyleRun(workPaint, len, fm, nativeStaticLayoutPtr);
} else {
// Use original text. Shouldn't matter.
wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
mTextStart + mPos + len, fm);
- if (mBuilder == null) {
+ if (nativeStaticLayoutPtr == 0) {
float[] w = mWidths;
w[mPos] = wid;
for (int i = mPos + 1, e = mPos + len; i < e; i++)
w[i] = 0;
} else {
- mBuilder.addReplacementRun(paint, mPos, mPos + len, wid);
+ StaticLayout.addReplacementRun(nativeStaticLayoutPtr, paint, mPos, mPos + len, wid);
}
mPos += len;
}
@@ -253,6 +253,11 @@
return wid;
}
+ float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
+ Paint.FontMetricsInt fm) {
+ return addStyleRun(paint, spans, len, fm, 0 /* native ptr */);
+ }
+
int breakText(int limit, boolean forwards, float width) {
float[] w = mWidths;
if (forwards) {
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 5c60188..c0fc44f 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -32,6 +32,9 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
import java.util.Arrays;
/**
@@ -57,9 +60,7 @@
* default values.
*/
public final static class Builder {
- private Builder() {
- mNativePtr = nNewBuilder();
- }
+ private Builder() {}
/**
* Obtain a builder for constructing StaticLayout objects.
@@ -116,13 +117,11 @@
b.mRightIndents = null;
b.mLeftPaddings = null;
b.mRightPaddings = null;
- nFinishBuilder(b.mNativePtr);
sPool.release(b);
}
// release any expensive state
/* package */ void finish() {
- nFinishBuilder(mNativePtr);
mText = null;
mPaint = null;
mLeftIndents = null;
@@ -405,32 +404,6 @@
}
/**
- * Measurement and break iteration is done in native code. The protocol for using
- * the native code is as follows.
- *
- * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
- * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
- * future).
- *
- * Then, for each run within the paragraph:
- * - one of the following, depending on the type of run:
- * + addStyleRun (a text run, to be measured in native code)
- * + addReplacementRun (a replacement run, width is given)
- *
- * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
- *
- * After all paragraphs, call finish() to release expensive buffers.
- */
-
- /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
- nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
- }
-
- /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
- nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
- }
-
- /**
* Build the {@link StaticLayout} after options have been set.
*
* <p>Note: the builder object must not be reused in any way after calling this
@@ -446,17 +419,6 @@
return result;
}
- @Override
- protected void finalize() throws Throwable {
- try {
- nFreeBuilder(mNativePtr);
- } finally {
- super.finalize();
- }
- }
-
- /* package */ long mNativePtr;
-
private CharSequence mText;
private int mStart;
private int mEnd;
@@ -694,270 +656,294 @@
indents = null;
}
- int paraEnd;
- for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
- paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
- if (paraEnd < 0)
- paraEnd = bufEnd;
- else
- paraEnd++;
+ final long nativePtr = nInit(
+ b.mBreakStrategy, b.mHyphenationFrequency,
+ // TODO: Support more justification mode, e.g. letter spacing, stretching.
+ b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
+ indents, mLeftPaddings, mRightPaddings);
- int firstWidthLineCount = 1;
- int firstWidth = outerWidth;
- int restWidth = outerWidth;
+ try {
+ int paraEnd;
+ for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
+ paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
+ if (paraEnd < 0) {
+ paraEnd = bufEnd;
+ } else {
+ paraEnd++;
+ }
- LineHeightSpan[] chooseHt = null;
+ int firstWidthLineCount = 1;
+ int firstWidth = outerWidth;
+ int restWidth = outerWidth;
- if (spanned != null) {
- LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
- LeadingMarginSpan.class);
- for (int i = 0; i < sp.length; i++) {
- LeadingMarginSpan lms = sp[i];
- firstWidth -= sp[i].getLeadingMargin(true);
- restWidth -= sp[i].getLeadingMargin(false);
+ LineHeightSpan[] chooseHt = null;
- // LeadingMarginSpan2 is odd. The count affects all
- // leading margin spans, not just this particular one
- if (lms instanceof LeadingMarginSpan2) {
- LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
- firstWidthLineCount = Math.max(firstWidthLineCount,
- lms2.getLeadingMarginLineCount());
+ if (spanned != null) {
+ LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+ LeadingMarginSpan.class);
+ for (int i = 0; i < sp.length; i++) {
+ LeadingMarginSpan lms = sp[i];
+ firstWidth -= sp[i].getLeadingMargin(true);
+ restWidth -= sp[i].getLeadingMargin(false);
+
+ // LeadingMarginSpan2 is odd. The count affects all
+ // leading margin spans, not just this particular one
+ if (lms instanceof LeadingMarginSpan2) {
+ LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+ firstWidthLineCount = Math.max(firstWidthLineCount,
+ lms2.getLeadingMarginLineCount());
+ }
+ }
+
+ chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+
+ if (chooseHt.length == 0) {
+ chooseHt = null; // So that out() would not assume it has any contents
+ } else {
+ if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+ chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+ }
+
+ for (int i = 0; i < chooseHt.length; i++) {
+ int o = spanned.getSpanStart(chooseHt[i]);
+
+ if (o < paraStart) {
+ // starts in this layout, before the
+ // current paragraph
+
+ chooseHtv[i] = getLineTop(getLineForOffset(o));
+ } else {
+ // starts in this paragraph
+
+ chooseHtv[i] = v;
+ }
+ }
}
}
- chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+ measured.setPara(source, paraStart, paraEnd, textDir);
+ char[] chs = measured.mChars;
+ float[] widths = measured.mWidths;
+ byte[] chdirs = measured.mLevels;
+ int dir = measured.mDir;
+ boolean easy = measured.mEasy;
- if (chooseHt.length == 0) {
- chooseHt = null; // So that out() would not assume it has any contents
- } else {
- if (chooseHtv == null ||
- chooseHtv.length < chooseHt.length) {
- chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+ // tab stop locations
+ int[] variableTabStops = null;
+ if (spanned != null) {
+ TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+ paraEnd, TabStopSpan.class);
+ if (spans.length > 0) {
+ int[] stops = new int[spans.length];
+ for (int i = 0; i < spans.length; i++) {
+ stops[i] = spans[i].getTabStop();
+ }
+ Arrays.sort(stops, 0, stops.length);
+ variableTabStops = stops;
+ }
+ }
+
+ // measurement has to be done before performing line breaking
+ // but we don't want to recompute fontmetrics or span ranges the
+ // second time, so we cache those and then use those stored values
+ int fmCacheCount = 0;
+ int spanEndCacheCount = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ if (fmCacheCount * 4 >= fmCache.length) {
+ int[] grow = new int[fmCacheCount * 4 * 2];
+ System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
+ fmCache = grow;
}
- for (int i = 0; i < chooseHt.length; i++) {
- int o = spanned.getSpanStart(chooseHt[i]);
+ if (spanEndCacheCount >= spanEndCache.length) {
+ int[] grow = new int[spanEndCacheCount * 2];
+ System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
+ spanEndCache = grow;
+ }
- if (o < paraStart) {
- // starts in this layout, before the
- // current paragraph
+ if (spanned == null) {
+ spanEnd = paraEnd;
+ int spanLen = spanEnd - spanStart;
+ measured.addStyleRun(paint, spanLen, fm, nativePtr);
+ } else {
+ spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+ MetricAffectingSpan.class);
+ int spanLen = spanEnd - spanStart;
+ MetricAffectingSpan[] spans =
+ spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, spanned,
+ MetricAffectingSpan.class);
+ measured.addStyleRun(paint, spans, spanLen, fm, nativePtr);
+ }
- chooseHtv[i] = getLineTop(getLineForOffset(o));
+ // the order of storage here (top, bottom, ascent, descent) has to match the
+ // code below where these values are retrieved
+ fmCache[fmCacheCount * 4 + 0] = fm.top;
+ fmCache[fmCacheCount * 4 + 1] = fm.bottom;
+ fmCache[fmCacheCount * 4 + 2] = fm.ascent;
+ fmCache[fmCacheCount * 4 + 3] = fm.descent;
+ fmCacheCount++;
+
+ spanEndCache[spanEndCacheCount] = spanEnd;
+ spanEndCacheCount++;
+ }
+
+ int breakCount = nComputeLineBreaks(
+ nativePtr,
+
+ // Inputs
+ chs,
+ paraEnd - paraStart,
+ firstWidth,
+ firstWidthLineCount,
+ restWidth,
+ variableTabStops,
+ TAB_INCREMENT,
+ mLineCount,
+
+ // Outputs
+ lineBreaks,
+ lineBreaks.breaks.length,
+ lineBreaks.breaks,
+ lineBreaks.widths,
+ lineBreaks.ascents,
+ lineBreaks.descents,
+ lineBreaks.flags,
+ widths);
+
+ final int[] breaks = lineBreaks.breaks;
+ final float[] lineWidths = lineBreaks.widths;
+ final float[] ascents = lineBreaks.ascents;
+ final float[] descents = lineBreaks.descents;
+ final int[] flags = lineBreaks.flags;
+
+ final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+ final boolean ellipsisMayBeApplied = ellipsize != null
+ && (ellipsize == TextUtils.TruncateAt.END
+ || (mMaximumVisibleLineCount == 1
+ && ellipsize != TextUtils.TruncateAt.MARQUEE));
+ if (0 < remainingLineCount && remainingLineCount < breakCount
+ && ellipsisMayBeApplied) {
+ // Calculate width and flag.
+ float width = 0;
+ int flag = 0; // XXX May need to also have starting hyphen edit
+ for (int i = remainingLineCount - 1; i < breakCount; i++) {
+ if (i == breakCount - 1) {
+ width += lineWidths[i];
} else {
- // starts in this paragraph
-
- chooseHtv[i] = v;
+ for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+ width += widths[j];
+ }
}
+ flag |= flags[i] & TAB_MASK;
}
- }
- }
+ // Treat the last line and overflowed lines as a single line.
+ breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+ lineWidths[remainingLineCount - 1] = width;
+ flags[remainingLineCount - 1] = flag;
- measured.setPara(source, paraStart, paraEnd, textDir, b);
- char[] chs = measured.mChars;
- float[] widths = measured.mWidths;
- byte[] chdirs = measured.mLevels;
- int dir = measured.mDir;
- boolean easy = measured.mEasy;
-
- // tab stop locations
- int[] variableTabStops = null;
- if (spanned != null) {
- TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
- paraEnd, TabStopSpan.class);
- if (spans.length > 0) {
- int[] stops = new int[spans.length];
- for (int i = 0; i < spans.length; i++) {
- stops[i] = spans[i].getTabStop();
- }
- Arrays.sort(stops, 0, stops.length);
- variableTabStops = stops;
- }
- }
-
- nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
- firstWidth, firstWidthLineCount, restWidth,
- variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
- // TODO: Support more justification mode, e.g. letter spacing, stretching.
- b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
- // TODO: indents and paddings don't need to get passed to native code for every
- // paragraph. Pass them to native code just once.
- indents, mLeftPaddings, mRightPaddings, mLineCount);
-
- // measurement has to be done before performing line breaking
- // but we don't want to recompute fontmetrics or span ranges the
- // second time, so we cache those and then use those stored values
- int fmCacheCount = 0;
- int spanEndCacheCount = 0;
- for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
- if (fmCacheCount * 4 >= fmCache.length) {
- int[] grow = new int[fmCacheCount * 4 * 2];
- System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
- fmCache = grow;
+ breakCount = remainingLineCount;
}
- if (spanEndCacheCount >= spanEndCache.length) {
- int[] grow = new int[spanEndCacheCount * 2];
- System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
- spanEndCache = grow;
- }
+ // here is the offset of the starting character of the line we are currently
+ // measuring
+ int here = paraStart;
- if (spanned == null) {
- spanEnd = paraEnd;
- int spanLen = spanEnd - spanStart;
- measured.addStyleRun(paint, spanLen, fm);
- } else {
- spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
- MetricAffectingSpan.class);
- int spanLen = spanEnd - spanStart;
- MetricAffectingSpan[] spans =
- spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
- spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
- measured.addStyleRun(paint, spans, spanLen, fm);
- }
+ int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+ int fmCacheIndex = 0;
+ int spanEndCacheIndex = 0;
+ int breakIndex = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ // retrieve end of span
+ spanEnd = spanEndCache[spanEndCacheIndex++];
- // the order of storage here (top, bottom, ascent, descent) has to match the code below
- // where these values are retrieved
- fmCache[fmCacheCount * 4 + 0] = fm.top;
- fmCache[fmCacheCount * 4 + 1] = fm.bottom;
- fmCache[fmCacheCount * 4 + 2] = fm.ascent;
- fmCache[fmCacheCount * 4 + 3] = fm.descent;
- fmCacheCount++;
+ // retrieve cached metrics, order matches above
+ fm.top = fmCache[fmCacheIndex * 4 + 0];
+ fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+ fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+ fm.descent = fmCache[fmCacheIndex * 4 + 3];
+ fmCacheIndex++;
- spanEndCache[spanEndCacheCount] = spanEnd;
- spanEndCacheCount++;
- }
-
- int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
- lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
- lineBreaks.breaks.length, widths);
-
- final int[] breaks = lineBreaks.breaks;
- final float[] lineWidths = lineBreaks.widths;
- final float[] ascents = lineBreaks.ascents;
- final float[] descents = lineBreaks.descents;
- final int[] flags = lineBreaks.flags;
-
- final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
- final boolean ellipsisMayBeApplied = ellipsize != null
- && (ellipsize == TextUtils.TruncateAt.END
- || (mMaximumVisibleLineCount == 1
- && ellipsize != TextUtils.TruncateAt.MARQUEE));
- if (0 < remainingLineCount && remainingLineCount < breakCount
- && ellipsisMayBeApplied) {
- // Calculate width and flag.
- float width = 0;
- int flag = 0; // XXX May need to also have starting hyphen edit
- for (int i = remainingLineCount - 1; i < breakCount; i++) {
- if (i == breakCount - 1) {
- width += lineWidths[i];
- } else {
- for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
- width += widths[j];
- }
- }
- flag |= flags[i] & TAB_MASK;
- }
- // Treat the last line and overflowed lines as a single line.
- breaks[remainingLineCount - 1] = breaks[breakCount - 1];
- lineWidths[remainingLineCount - 1] = width;
- flags[remainingLineCount - 1] = flag;
-
- breakCount = remainingLineCount;
- }
-
- // here is the offset of the starting character of the line we are currently measuring
- int here = paraStart;
-
- int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
- int fmCacheIndex = 0;
- int spanEndCacheIndex = 0;
- int breakIndex = 0;
- for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
- // retrieve end of span
- spanEnd = spanEndCache[spanEndCacheIndex++];
-
- // retrieve cached metrics, order matches above
- fm.top = fmCache[fmCacheIndex * 4 + 0];
- fm.bottom = fmCache[fmCacheIndex * 4 + 1];
- fm.ascent = fmCache[fmCacheIndex * 4 + 2];
- fm.descent = fmCache[fmCacheIndex * 4 + 3];
- fmCacheIndex++;
-
- if (fm.top < fmTop) {
- fmTop = fm.top;
- }
- if (fm.ascent < fmAscent) {
- fmAscent = fm.ascent;
- }
- if (fm.descent > fmDescent) {
- fmDescent = fm.descent;
- }
- if (fm.bottom > fmBottom) {
- fmBottom = fm.bottom;
- }
-
- // skip breaks ending before current span range
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
- breakIndex++;
- }
-
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
- int endPos = paraStart + breaks[breakIndex];
-
- boolean moreChars = (endPos < bufEnd);
-
- final int ascent = fallbackLineSpacing
- ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
- : fmAscent;
- final int descent = fallbackLineSpacing
- ? Math.max(fmDescent, Math.round(descents[breakIndex]))
- : fmDescent;
- v = out(source, here, endPos,
- ascent, descent, fmTop, fmBottom,
- v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
- needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
- addLastLineSpacing, chs, widths, paraStart, ellipsize,
- ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
-
- if (endPos < spanEnd) {
- // preserve metrics for current span
+ if (fm.top < fmTop) {
fmTop = fm.top;
- fmBottom = fm.bottom;
+ }
+ if (fm.ascent < fmAscent) {
fmAscent = fm.ascent;
+ }
+ if (fm.descent > fmDescent) {
fmDescent = fm.descent;
- } else {
- fmTop = fmBottom = fmAscent = fmDescent = 0;
+ }
+ if (fm.bottom > fmBottom) {
+ fmBottom = fm.bottom;
}
- here = endPos;
- breakIndex++;
-
- if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
- return;
+ // skip breaks ending before current span range
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+ breakIndex++;
}
+
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+ int endPos = paraStart + breaks[breakIndex];
+
+ boolean moreChars = (endPos < bufEnd);
+
+ final int ascent = fallbackLineSpacing
+ ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+ : fmAscent;
+ final int descent = fallbackLineSpacing
+ ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+ : fmDescent;
+ v = out(source, here, endPos,
+ ascent, descent, fmTop, fmBottom,
+ v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+ flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd,
+ includepad, trackpad, addLastLineSpacing, chs, widths, paraStart,
+ ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint,
+ moreChars);
+
+ if (endPos < spanEnd) {
+ // preserve metrics for current span
+ fmTop = fm.top;
+ fmBottom = fm.bottom;
+ fmAscent = fm.ascent;
+ fmDescent = fm.descent;
+ } else {
+ fmTop = fmBottom = fmAscent = fmDescent = 0;
+ }
+
+ here = endPos;
+ breakIndex++;
+
+ if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+ return;
+ }
+ }
+ }
+
+ if (paraEnd == bufEnd) {
+ break;
}
}
- if (paraEnd == bufEnd)
- break;
- }
+ if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
+ && mLineCount < mMaximumVisibleLineCount) {
+ measured.setPara(source, bufEnd, bufEnd, textDir);
- if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
- mLineCount < mMaximumVisibleLineCount) {
- measured.setPara(source, bufEnd, bufEnd, textDir, b);
+ paint.getFontMetricsInt(fm);
- paint.getFontMetricsInt(fm);
-
- v = out(source,
- bufEnd, bufEnd, fm.ascent, fm.descent,
- fm.top, fm.bottom,
- v,
- spacingmult, spacingadd, null,
- null, fm, 0,
- needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
- includepad, trackpad, addLastLineSpacing, null,
- null, bufStart, ellipsize,
- ellipsizedWidth, 0, paint, false);
+ v = out(source,
+ bufEnd, bufEnd, fm.ascent, fm.descent,
+ fm.top, fm.bottom,
+ v,
+ spacingmult, spacingadd, null,
+ null, fm, 0,
+ needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
+ includepad, trackpad, addLastLineSpacing, null,
+ null, bufStart, ellipsize,
+ ellipsizedWidth, 0, paint, false);
+ }
+ } finally {
+ nFinish(nativePtr);
}
}
@@ -1487,26 +1473,51 @@
mMaxLineHeight : super.getHeight();
}
- private static native long nNewBuilder();
- private static native void nFreeBuilder(long nativePtr);
- private static native void nFinishBuilder(long nativePtr);
+ /**
+ * Measurement and break iteration is done in native code. The protocol for using
+ * the native code is as follows.
+ *
+ * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
+ * following:
+ *
+ * - Call one of the following methods for each run within the paragraph depending on the type
+ * of run:
+ * + addStyleRun (a text run, to be measured in native code)
+ * + addReplacementRun (a replacement run, width is given)
+ *
+ * - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
+ *
+ * After all paragraphs, call finish() to release expensive buffers.
+ */
- // Set up paragraph text and settings; done as one big method to minimize jni crossings
- private static native void nSetupParagraph(
- /* non zero */ long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
- @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount,
- @FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops,
- int defaultTabStop, @BreakStrategy int breakStrategy,
- @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
- @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
- @IntRange(from = 0) int indentsOffset);
+ /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end,
+ boolean isRtl) {
+ nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl);
+ }
- // TODO: Make this method CriticalNative once native code defers doing layouts.
+ /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end,
+ float width) {
+ nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width);
+ }
+
+ @FastNative
+ private static native long nInit(
+ @BreakStrategy int breakStrategy,
+ @HyphenationFrequency int hyphenationFrequency,
+ boolean isJustified,
+ @Nullable int[] indents,
+ @Nullable int[] leftPaddings,
+ @Nullable int[] rightPaddings);
+
+ @CriticalNative
+ private static native void nFinish(long nativePtr);
+
+ @CriticalNative
private static native void nAddStyleRun(
/* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
@IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
- // TODO: Make this method CriticalNative once native code defers doing layouts.
+ @CriticalNative
private static native void nAddReplacementRun(
/* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
@IntRange(from = 0) int start, @IntRange(from = 0) int end,
@@ -1519,10 +1530,28 @@
// arrays do not have to be resized
// The individual character widths will be returned in charWidths. The length of charWidths must
// be at least the length of the text.
- private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
- int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
- float[] recycleDescents, int[] recycleFlags, int recycleLength,
- float[] charWidths);
+ private static native int nComputeLineBreaks(
+ /* non zero */ long nativePtr,
+
+ // Inputs
+ @NonNull char[] text,
+ @IntRange(from = 0) int length,
+ @FloatRange(from = 0.0f) float firstWidth,
+ @IntRange(from = 0) int firstWidthLineCount,
+ @FloatRange(from = 0.0f) float restWidth,
+ @Nullable int[] variableTabStops,
+ int defaultTabStop,
+ @IntRange(from = 0) int indentsOffset,
+
+ // Outputs
+ @NonNull LineBreaks recycle,
+ @IntRange(from = 0) int recycleLength,
+ @NonNull int[] recycleBreaks,
+ @NonNull float[] recycleWidths,
+ @NonNull float[] recycleAscents,
+ @NonNull float[] recycleDescents,
+ @NonNull int[] recycleFlags,
+ @NonNull float[] charWidths);
private int mLineCount;
private int mTopPadding, mBottomPadding;
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 20c0ed8..86cc0141 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -28,6 +28,7 @@
import android.text.style.ReplacementSpan;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
@@ -44,7 +45,8 @@
*
* @hide
*/
-class TextLine {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class TextLine {
private static final boolean DEBUG = false;
private TextPaint mPaint;
@@ -82,7 +84,8 @@
*
* @return an uninitialized TextLine
*/
- static TextLine obtain() {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static TextLine obtain() {
TextLine tl;
synchronized (sCached) {
for (int i = sCached.length; --i >= 0;) {
@@ -107,7 +110,8 @@
* @return null, as a convenience from clearing references to the provided
* TextLine
*/
- static TextLine recycle(TextLine tl) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static TextLine recycle(TextLine tl) {
tl.mText = null;
tl.mPaint = null;
tl.mDirections = null;
@@ -142,7 +146,8 @@
* @param hasTabs true if the line might contain tabs
* @param tabStops the tabStops. Can be null.
*/
- void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
Directions directions, boolean hasTabs, TabStops tabStops) {
mPaint = paint;
mText = text;
@@ -196,7 +201,8 @@
/**
* Justify the line to the given width.
*/
- void justify(float justifyWidth) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void justify(float justifyWidth) {
int end = mLen;
while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) {
end--;
@@ -277,7 +283,8 @@
* @param fmi receives font metrics information, can be null
* @return the signed width of the line
*/
- float metrics(FontMetricsInt fmi) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public float metrics(FontMetricsInt fmi) {
return measure(mLen, false, fmi);
}
@@ -1165,23 +1172,18 @@
}
private boolean isStretchableWhitespace(int ch) {
- // TODO: Support other stretchable whitespace. (Bug: 34013491)
- return ch == 0x0020 || ch == 0x00A0;
- }
-
- private int nextStretchableSpace(int start, int end) {
- for (int i = start; i < end; i++) {
- final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
- if (isStretchableWhitespace(c)) return i;
- }
- return end;
+ // TODO: Support NBSP and other stretchable whitespace (b/34013491 and b/68204709).
+ return ch == 0x0020;
}
/* Return the number of spaces in the text line, for the purpose of justification */
private int countStretchableSpaces(int start, int end) {
int count = 0;
- for (int i = start; i < end; i = nextStretchableSpace(i + 1, end)) {
- count++;
+ for (int i = start; i < end; i++) {
+ final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
+ if (isStretchableWhitespace(c)) {
+ count++;
+ }
}
return count;
}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 68afeec..cbdaa69 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1519,7 +1519,7 @@
}
// XXX this is probably ok, but need to look at it more
- tempMt.setPara(format, 0, format.length(), textDir, null);
+ tempMt.setPara(format, 0, format.length(), textDir);
float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);
if (w + moreWid <= avail) {
@@ -1541,7 +1541,7 @@
private static float setPara(MeasuredText mt, TextPaint paint,
CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
- mt.setPara(text, start, end, textDir, null);
+ mt.setPara(text, start, end, textDir);
float width;
Spanned sp = text instanceof Spanned ? (Spanned) text : null;
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
index 43a9789..a94806a 100644
--- a/core/java/android/util/proto/ProtoOutputStream.java
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -127,42 +127,48 @@
public static final long FIELD_TYPE_UNKNOWN = 0;
+ /**
+ * The types are copied from external/protobuf/src/google/protobuf/descriptor.h directly,
+ * so no extra mapping needs to be maintained in this case.
+ */
public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT64 = 3L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT64 = 4L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT32 = 5L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED64 = 6L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED32 = 7L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BOOL = 8L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_STRING = 9L << FIELD_TYPE_SHIFT;
+// public static final long FIELD_TYPE_GROUP = 10L << FIELD_TYPE_SHIFT; // Deprecated.
+ public static final long FIELD_TYPE_MESSAGE = 11L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BYTES = 12L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT32 = 13L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_ENUM = 14L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED32 = 15L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED64 = 16L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT32 = 17L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT64 = 18L << FIELD_TYPE_SHIFT;
private static final String[] FIELD_TYPE_NAMES = new String[] {
"Double",
"Float",
- "Int32",
"Int64",
- "UInt32",
"UInt64",
- "SInt32",
- "SInt64",
- "Fixed32",
+ "Int32",
"Fixed64",
- "SFixed32",
- "SFixed64",
+ "Fixed32",
"Bool",
"String",
+ "Group", // This field is deprecated but reserved here for indexing.
+ "Message",
"Bytes",
+ "UInt32",
"Enum",
- "Object",
+ "SFixed32",
+ "SFixed64",
+ "SInt32",
+ "SInt64",
};
//
@@ -867,21 +873,21 @@
assertNotCompacted();
final int id = (int)fieldId;
- switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+ switch ((int) ((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
// bytes
- case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
writeBytesImpl(id, val);
break;
- case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
- case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
writeRepeatedBytesImpl(id, val);
break;
// Object
- case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
writeObjectImpl(id, val);
break;
- case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
- case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
writeRepeatedObjectImpl(id, val);
break;
// nothing else allowed
@@ -899,7 +905,7 @@
assertNotCompacted();
final int id = (int)fieldId;
- if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_OBJECT) {
+ if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_MESSAGE) {
final long count = fieldId & FIELD_COUNT_MASK;
if (count == FIELD_COUNT_SINGLE) {
return startObjectImpl(id, false);
@@ -2091,7 +2097,7 @@
@Deprecated
public long startObject(long fieldId) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
return startObjectImpl(id, false);
}
@@ -2119,7 +2125,7 @@
@Deprecated
public long startRepeatedObject(long fieldId) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
return startObjectImpl(id, true);
}
@@ -2217,7 +2223,7 @@
@Deprecated
public void writeObject(long fieldId, byte[] value) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
writeObjectImpl(id, value);
}
@@ -2237,7 +2243,7 @@
@Deprecated
public void writeRepeatedObject(long fieldId, byte[] value) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
writeRepeatedObjectImpl(id, value);
}
@@ -2296,7 +2302,7 @@
final String typeString = getFieldTypeString(fieldType);
if (typeString != null && countString != null) {
final StringBuilder sb = new StringBuilder();
- if (expectedType == FIELD_TYPE_OBJECT) {
+ if (expectedType == FIELD_TYPE_MESSAGE) {
sb.append("start");
} else {
sb.append("write");
@@ -2306,7 +2312,7 @@
sb.append(" called for field ");
sb.append((int)fieldId);
sb.append(" which should be used with ");
- if (fieldType == FIELD_TYPE_OBJECT) {
+ if (fieldType == FIELD_TYPE_MESSAGE) {
sb.append("start");
} else {
sb.append("write");
@@ -2321,7 +2327,7 @@
throw new IllegalArgumentException(sb.toString());
} else {
final StringBuilder sb = new StringBuilder();
- if (expectedType == FIELD_TYPE_OBJECT) {
+ if (expectedType == FIELD_TYPE_MESSAGE) {
sb.append("start");
} else {
sb.append("write");
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 6e49bac..65bde49 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -16,7 +16,7 @@
package android.view;
-import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.app.IAssistDataReceiver;
import com.android.internal.os.IResultReceiver;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
@@ -272,7 +272,7 @@
/**
* Used only for assist -- request a screenshot of the current application.
*/
- boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver);
+ boolean requestAssistScreenshot(IAssistDataReceiver receiver);
/**
* Called by the status bar to notify Views of changes to System UI visiblity.
@@ -385,7 +385,7 @@
/**
* Create an input consumer by name.
*/
- void createInputConsumer(String name, out InputChannel inputChannel);
+ void createInputConsumer(IBinder token, String name, out InputChannel inputChannel);
/**
* Destroy an input consumer by name. This method will also dispose the input channels
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index ff027a9..6f8315a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -16,27 +16,22 @@
package android.view;
-import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
-
import android.annotation.Size;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
-import android.graphics.Point;
-import android.graphics.PointF;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Binder;
-import android.os.Debug;
import android.os.IBinder;
+import android.os.Process;
+import android.os.UserHandle;
import android.util.Log;
import android.view.Surface.OutOfResourcesException;
-
import dalvik.system.CloseGuard;
+import libcore.util.NativeAllocationRegistry;
import java.io.Closeable;
-import libcore.util.NativeAllocationRegistry;
-
/**
* SurfaceControl
* @hide
@@ -60,6 +55,8 @@
private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
boolean allLayers, boolean useIdentityTransform);
+ private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer,
+ int rotation);
private static native long nativeCreateTransaction();
private static native long nativeGetNativeTransactionFinalizer();
@@ -186,7 +183,7 @@
/**
* Surface creation flag: Indicates that the surface must be considered opaque,
- * even if its pixel format is set to translucent. This can be useful if an
+ * even if its pixel format contains an alpha channel. This can be useful if an
* application needs full RGBA 8888 support for instance but will
* still draw every pixel opaque.
* <p>
@@ -307,6 +304,203 @@
public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731;
/**
+ * Builder class for {@link SurfaceControl} objects.
+ */
+ public static class Builder {
+ private SurfaceSession mSession;
+ private int mFlags = HIDDEN;
+ private int mWidth;
+ private int mHeight;
+ private int mFormat = PixelFormat.OPAQUE;
+ private String mName;
+ private SurfaceControl mParent;
+ private int mWindowType;
+ private int mOwnerUid;
+
+ /**
+ * Begin building a SurfaceControl with a given {@link SurfaceSession}.
+ *
+ * @param session The {@link SurfaceSession} with which to eventually construct the surface.
+ */
+ public Builder(SurfaceSession session) {
+ mSession = session;
+ }
+
+ /**
+ * Construct a new {@link SurfaceControl} with the set parameters.
+ */
+ public SurfaceControl build() {
+ if (mWidth <= 0 || mHeight <= 0) {
+ throw new IllegalArgumentException(
+ "width and height must be set");
+ }
+ return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat,
+ mFlags, mParent, mWindowType, mOwnerUid);
+ }
+
+ /**
+ * Set a debugging-name for the SurfaceControl.
+ *
+ * @param name A name to identify the Surface in debugging.
+ */
+ public Builder setName(String name) {
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Set the initial size of the controlled surface's buffers in pixels.
+ *
+ * @param width The buffer width in pixels.
+ * @param height The buffer height in pixels.
+ */
+ public Builder setSize(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException(
+ "width and height must be positive");
+ }
+ mWidth = width;
+ mHeight = height;
+ return this;
+ }
+
+ /**
+ * Set the pixel format of the controlled surface's buffers, using constants from
+ * {@link android.graphics.PixelFormat}.
+ */
+ public Builder setFormat(@PixelFormat.Format int format) {
+ mFormat = format;
+ return this;
+ }
+
+ /**
+ * Specify if the app requires a hardware-protected path to
+ * an external display sync. If protected content is enabled, but
+ * such a path is not available, then the controlled Surface will
+ * not be displayed.
+ *
+ * @param protectedContent Whether to require a protected sink.
+ */
+ public Builder setProtected(boolean protectedContent) {
+ if (protectedContent) {
+ mFlags |= PROTECTED_APP;
+ } else {
+ mFlags &= ~PROTECTED_APP;
+ }
+ return this;
+ }
+
+ /**
+ * Specify whether the Surface contains secure content. If true, the system
+ * will prevent the surfaces content from being copied by another process. In
+ * particular screenshots and VNC servers will be disabled. This is however
+ * not a complete prevention of readback as {@link #setProtected}.
+ */
+ public Builder setSecure(boolean secure) {
+ if (secure) {
+ mFlags |= SECURE;
+ } else {
+ mFlags &= ~SECURE;
+ }
+ return this;
+ }
+
+ /**
+ * Indicates whether the surface must be considered opaque,
+ * even if its pixel format is set to translucent. This can be useful if an
+ * application needs full RGBA 8888 support for instance but will
+ * still draw every pixel opaque.
+ * <p>
+ * This flag only determines whether opacity will be sampled from the alpha channel.
+ * Plane-alpha from calls to setAlpha() can still result in blended composition
+ * regardless of the opaque setting.
+ *
+ * Combined effects are (assuming a buffer format with an alpha channel):
+ * <ul>
+ * <li>OPAQUE + alpha(1.0) == opaque composition
+ * <li>OPAQUE + alpha(0.x) == blended composition
+ * <li>OPAQUE + alpha(0.0) == no composition
+ * <li>!OPAQUE + alpha(1.0) == blended composition
+ * <li>!OPAQUE + alpha(0.x) == blended composition
+ * <li>!OPAQUE + alpha(0.0) == no composition
+ * </ul>
+ * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
+ * were set automatically.
+ * @param opaque Whether the Surface is OPAQUE.
+ */
+ public Builder setOpaque(boolean opaque) {
+ if (opaque) {
+ mFlags |= OPAQUE;
+ } else {
+ mFlags &= ~OPAQUE;
+ }
+ return this;
+ }
+
+ /**
+ * Set a parent surface for our new SurfaceControl.
+ *
+ * Child surfaces are constrained to the onscreen region of their parent.
+ * Furthermore they stack relatively in Z order, and inherit the transformation
+ * of the parent.
+ *
+ * @param parent The parent control.
+ */
+ public Builder setParent(SurfaceControl parent) {
+ mParent = parent;
+ return this;
+ }
+
+ /**
+ * Set surface metadata.
+ *
+ * Currently these are window-types as per {@link WindowManager.LayoutParams} and
+ * owner UIDs. Child surfaces inherit their parents
+ * metadata so only the WindowManager needs to set this on root Surfaces.
+ *
+ * @param windowType A window-type
+ * @param ownerUid UID of the window owner.
+ */
+ public Builder setMetadata(int windowType, int ownerUid) {
+ if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
+ throw new UnsupportedOperationException(
+ "It only makes sense to set Surface metadata from the WindowManager");
+ }
+ mWindowType = windowType;
+ mOwnerUid = ownerUid;
+ return this;
+ }
+
+ /**
+ * Indicate whether a 'ColorLayer' is to be constructed.
+ *
+ * Color layers will not have an associated BufferQueue and will instead always render a
+ * solid color (that is, solid before plane alpha). Currently that color is black.
+ *
+ * @param isColorLayer Whether to create a color layer.
+ */
+ public Builder setColorLayer(boolean isColorLayer) {
+ if (isColorLayer) {
+ mFlags |= FX_SURFACE_DIM;
+ } else {
+ mFlags &= ~FX_SURFACE_DIM;
+ }
+ return this;
+ }
+
+ /**
+ * Set 'Surface creation flags' such as {@link HIDDEN}, {@link SECURE}.
+ *
+ * TODO: Finish conversion to individual builder methods?
+ * @param flags The combined flags
+ */
+ public Builder setFlags(int flags) {
+ mFlags = flags;
+ return this;
+ }
+ }
+
+ /**
* Create a surface with a name.
* <p>
* The surface creation flags specify what kind of surface to create and
@@ -331,19 +525,7 @@
*
* @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
*/
- public SurfaceControl(SurfaceSession session,
- String name, int w, int h, int format, int flags, int windowType, int ownerUid)
- throws OutOfResourcesException {
- this(session, name, w, h, format, flags, null, windowType, ownerUid);
- }
-
- public SurfaceControl(SurfaceSession session,
- String name, int w, int h, int format, int flags)
- throws OutOfResourcesException {
- this(session, name, w, h, format, flags, null, INVALID_WINDOW_TYPE, Binder.getCallingUid());
- }
-
- public SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
+ private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
SurfaceControl parent, int windowType, int ownerUid)
throws OutOfResourcesException {
if (session == null) {
@@ -533,7 +715,7 @@
}
}
- public void setRelativeLayer(IBinder relativeTo, int zorder) {
+ public void setRelativeLayer(SurfaceControl relativeTo, int zorder) {
checkNotReleased();
synchronized(SurfaceControl.class) {
sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder);
@@ -970,6 +1152,20 @@
minLayer, maxLayer, allLayers, useIdentityTransform);
}
+ /**
+ * Captures a layer and its children into the provided {@link Surface}.
+ *
+ * @param layerHandleToken The root layer to capture.
+ * @param consumer The {@link Surface} to capture the layer into.
+ * @param rotation Apply a custom clockwise rotation to the screenshot, i.e.
+ * Surface.ROTATION_0,90,180,270. Surfaceflinger will always capture in its
+ * native portrait orientation by default, so this is useful for returning
+ * captures that are independent of device orientation.
+ */
+ public static void captureLayers(IBinder layerHandleToken, Surface consumer, int rotation) {
+ nativeCaptureLayers(layerHandleToken, consumer, rotation);
+ }
+
public static class Transaction implements Closeable {
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
Transaction.class.getClassLoader(),
@@ -1035,9 +1231,9 @@
return this;
}
- public Transaction setRelativeLayer(SurfaceControl sc, IBinder relativeTo, int z) {
+ public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
- relativeTo, z);
+ relativeTo.getHandle(), z);
return this;
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 462dad3..4eab496e 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -532,10 +532,15 @@
mDeferredDestroySurfaceControl = mSurfaceControl;
updateOpaqueFlag();
- mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession,
- "SurfaceView - " + viewRoot.getTitle().toString(),
- mSurfaceWidth, mSurfaceHeight, mFormat,
- mSurfaceFlags);
+ final String name = "SurfaceView - " + viewRoot.getTitle().toString();
+
+ mSurfaceControl = new SurfaceControlWithBackground(
+ name,
+ (mSurfaceFlags & SurfaceControl.OPAQUE) != 0,
+ new SurfaceControl.Builder(mSurfaceSession)
+ .setSize(mSurfaceWidth, mSurfaceHeight)
+ .setFormat(mFormat)
+ .setFlags(mSurfaceFlags));
} else if (mSurfaceControl == null) {
return;
}
@@ -1098,13 +1103,15 @@
private boolean mOpaque = true;
public boolean mVisible = false;
- public SurfaceControlWithBackground(SurfaceSession s,
- String name, int w, int h, int format, int flags)
+ public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b)
throws Exception {
- super(s, name, w, h, format, flags);
- mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h,
- PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM);
- mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
+ super(b.setName(name).build());
+
+ mBackgroundControl = b.setName("Background for -" + name)
+ .setFormat(OPAQUE)
+ .setColorLayer(true)
+ .build();
+ mOpaque = opaque;
}
@Override
diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java
index cf36f43..dc50fa1 100644
--- a/core/java/android/view/TouchDelegate.java
+++ b/core/java/android/view/TouchDelegate.java
@@ -44,7 +44,7 @@
/**
* mBounds inflated to include some slop. This rect is to track whether the motion events
- * should be considered to be be within the delegate view.
+ * should be considered to be within the delegate view.
*/
private Rect mSlopBounds;
@@ -64,14 +64,12 @@
public static final int BELOW = 2;
/**
- * The touchable region of the View extends to the left of its
- * actual extent.
+ * The touchable region of the View extends to the left of its actual extent.
*/
public static final int TO_LEFT = 4;
/**
- * The touchable region of the View extends to the right of its
- * actual extent.
+ * The touchable region of the View extends to the right of its actual extent.
*/
public static final int TO_RIGHT = 8;
@@ -108,28 +106,24 @@
boolean handled = false;
switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Rect bounds = mBounds;
-
- if (bounds.contains(x, y)) {
- mDelegateTargeted = true;
- sendToDelegate = true;
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_MOVE:
- sendToDelegate = mDelegateTargeted;
- if (sendToDelegate) {
- Rect slopBounds = mSlopBounds;
- if (!slopBounds.contains(x, y)) {
- hit = false;
+ case MotionEvent.ACTION_DOWN:
+ mDelegateTargeted = mBounds.contains(x, y);
+ sendToDelegate = mDelegateTargeted;
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_MOVE:
+ sendToDelegate = mDelegateTargeted;
+ if (sendToDelegate) {
+ Rect slopBounds = mSlopBounds;
+ if (!slopBounds.contains(x, y)) {
+ hit = false;
+ }
}
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- sendToDelegate = mDelegateTargeted;
- mDelegateTargeted = false;
- break;
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ sendToDelegate = mDelegateTargeted;
+ mDelegateTargeted = false;
+ break;
}
if (sendToDelegate) {
final View delegateView = mDelegateView;
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 4500862..c44c8dd 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -92,7 +92,7 @@
* Defines the duration in milliseconds a user needs to hold down the
* appropriate button to enable the accessibility shortcut once it's configured.
*/
- private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1500;
+ private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1000;
/**
* Defines the duration in milliseconds we will wait to see if a touch event
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 99438d8..37829f0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2284,18 +2284,36 @@
}
}
- if (mFirst && sAlwaysAssignFocus) {
- // handle first focus request
- if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
- + mView.hasFocus());
- if (mView != null) {
- if (!mView.hasFocus()) {
- mView.restoreDefaultFocus();
- if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
- + mView.findFocus());
- } else {
- if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
- + mView.findFocus());
+ if (mFirst) {
+ if (sAlwaysAssignFocus) {
+ // handle first focus request
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
+ }
+ if (mView != null) {
+ if (!mView.hasFocus()) {
+ mView.restoreDefaultFocus();
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(mTag, "First: requested focused view=" + mView.findFocus());
+ }
+ } else {
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(mTag, "First: existing focused view=" + mView.findFocus());
+ }
+ }
+ }
+ } else {
+ // Some views (like ScrollView) won't hand focus to descendants that aren't within
+ // their viewport. Before layout, there's a good change these views are size 0
+ // which means no children can get focus. After layout, this view now has size, but
+ // is not guaranteed to hand-off focus to a focusable child (specifically, the edge-
+ // case where the child has a size prior to layout and thus won't trigger
+ // focusableViewAvailable).
+ View focused = mView.findFocus();
+ if (focused instanceof ViewGroup
+ && ((ViewGroup) focused).getDescendantFocusability()
+ == ViewGroup.FOCUS_AFTER_DESCENDANTS) {
+ focused.restoreDefaultFocus();
}
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 19213ca..c3d6c69 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -187,8 +187,11 @@
Log.i(LOG_TAG, "Window cache miss");
}
final long identityToken = Binder.clearCallingIdentity();
- window = connection.getWindow(accessibilityWindowId);
- Binder.restoreCallingIdentity(identityToken);
+ try {
+ window = connection.getWindow(accessibilityWindowId);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (window != null) {
sAccessibilityCache.addWindow(window);
return window;
@@ -225,8 +228,11 @@
Log.i(LOG_TAG, "Windows cache miss");
}
final long identityToken = Binder.clearCallingIdentity();
- windows = connection.getWindows();
- Binder.restoreCallingIdentity(identityToken);
+ try {
+ windows = connection.getWindows();
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (windows != null) {
sAccessibilityCache.setWindows(windows);
return windows;
@@ -283,10 +289,14 @@
}
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
- accessibilityWindowId, accessibilityNodeId, interactionId, this,
- prefetchFlags, Thread.currentThread().getId(), arguments);
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findAccessibilityNodeInfoByAccessibilityId(
+ accessibilityWindowId, accessibilityNodeId, interactionId, this,
+ prefetchFlags, Thread.currentThread().getId(), arguments);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
@@ -333,10 +343,15 @@
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findAccessibilityNodeInfosByViewId(
- accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findAccessibilityNodeInfosByViewId(
+ accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
@@ -381,10 +396,15 @@
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findAccessibilityNodeInfosByText(
- accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findAccessibilityNodeInfosByText(
+ accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
@@ -428,10 +448,15 @@
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findFocus(accessibilityWindowId,
- accessibilityNodeId, focusType, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findFocus(accessibilityWindowId,
+ accessibilityNodeId, focusType, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
@@ -472,10 +497,15 @@
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.focusSearch(accessibilityWindowId,
- accessibilityNodeId, direction, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.focusSearch(accessibilityWindowId,
+ accessibilityNodeId, direction, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
@@ -515,10 +545,15 @@
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.performAccessibilityAction(
- accessibilityWindowId, accessibilityNodeId, action, arguments,
- interactionId, this, Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.performAccessibilityAction(
+ accessibilityWindowId, accessibilityNodeId, action, arguments,
+ interactionId, this, Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
return getPerformAccessibilityActionResultAndClear(interactionId);
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 0b9bc57..35f6acb 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -436,8 +436,11 @@
// client using it is called through Binder from another process. Example: MMS
// app adds a SMS notification and the NotificationManagerService calls this method
long identityToken = Binder.clearCallingIdentity();
- service.sendAccessibilityEvent(event, userId);
- Binder.restoreCallingIdentity(identityToken);
+ try {
+ service.sendAccessibilityEvent(event, userId);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (DEBUG) {
Log.i(LOG_TAG, event + " sent");
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index e564fa3..6a0669b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -24,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
@@ -224,7 +225,7 @@
/**
* State where the autofill context was finished by the server because the autofill
- * service could not autofill the page.
+ * service could not autofill the activity.
*
* <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored,
* exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}).
@@ -242,6 +243,16 @@
public static final int STATE_SHOWING_SAVE_UI = 3;
/**
+ * State where the autofill is disabled because the service cannot autofill the activity at all.
+ *
+ * <p>In this state, every call is ignored, even {@link #requestAutofill(View)}
+ * (and {@link #requestAutofill(View, int, Rect)}).
+ *
+ * @hide
+ */
+ public static final int STATE_DISABLED_BY_SERVICE = 4;
+
+ /**
* Makes an authentication id from a request id and a dataset id.
*
* @param requestId The request id.
@@ -398,6 +409,11 @@
* Runs the specified action on the UI thread.
*/
void runOnUiThread(Runnable action);
+
+ /**
+ * Gets the complete component name of this client.
+ */
+ ComponentName getComponentName();
}
/**
@@ -506,7 +522,7 @@
* @return whether autofill is enabled for the current user.
*/
public boolean isEnabled() {
- if (!hasAutofillFeature()) {
+ if (!hasAutofillFeature() || isDisabledByService()) {
return false;
}
synchronized (mLock) {
@@ -580,19 +596,31 @@
notifyViewEntered(view, 0);
}
+ private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) {
+ if (isDisabledByService()) {
+ if (sVerbose) {
+ Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+ + ") on state " + getStateAsStringLocked());
+ }
+ return true;
+ }
+ if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) {
+ if (sVerbose) {
+ Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+ + ") on state " + getStateAsStringLocked());
+ }
+ return true;
+ }
+ return false;
+ }
+
private void notifyViewEntered(@NonNull View view, int flags) {
if (!hasAutofillFeature()) {
return;
}
AutofillCallback callback = null;
synchronized (mLock) {
- if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
- if (sVerbose) {
- Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
- + "): ignored on state " + getStateAsStringLocked());
- }
- return;
- }
+ if (shouldIgnoreViewEnteredLocked(view, flags)) return;
ensureServiceClientAddedIfNeededLocked();
@@ -717,14 +745,8 @@
}
AutofillCallback callback = null;
synchronized (mLock) {
- if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
- if (sVerbose) {
- Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
- + ", virtualId=" + virtualId
- + "): ignored on state " + getStateAsStringLocked());
- }
- return;
- }
+ if (shouldIgnoreViewEnteredLocked(view, flags)) return;
+
ensureServiceClientAddedIfNeededLocked();
if (!mEnabled) {
@@ -1059,13 +1081,13 @@
return;
}
try {
+ final AutofillClient client = getClientLocked();
mSessionId = mService.startSession(mContext.getActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
- mCallback != null, flags, mContext.getOpPackageName());
+ mCallback != null, flags, client.getComponentName());
if (mSessionId != NO_SESSION) {
mState = STATE_ACTIVE;
}
- final AutofillClient client = getClientLocked();
if (client != null) {
client.autofillCallbackResetableStateAvailable();
}
@@ -1120,14 +1142,14 @@
try {
if (restartIfNecessary) {
+ final AutofillClient client = getClientLocked();
final int newId = mService.updateOrRestartSession(mContext.getActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
- mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action);
+ mCallback != null, flags, client.getComponentName(), mSessionId, action);
if (newId != mSessionId) {
if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
mSessionId = newId;
mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE;
- final AutofillClient client = getClientLocked();
if (client != null) {
client.autofillCallbackResetableStateAvailable();
}
@@ -1250,18 +1272,32 @@
}
}
- private void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+ /** @hide */
+ public static final int SET_STATE_FLAG_ENABLED = 0x01;
+ /** @hide */
+ public static final int SET_STATE_FLAG_RESET_SESSION = 0x02;
+ /** @hide */
+ public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04;
+ /** @hide */
+ public static final int SET_STATE_FLAG_DEBUG = 0x08;
+ /** @hide */
+ public static final int SET_STATE_FLAG_VERBOSE = 0x10;
+
+ private void setState(int flags) {
+ if (sVerbose) Log.v(TAG, "setState(" + flags + ")");
synchronized (mLock) {
- mEnabled = enabled;
- if (!mEnabled || resetSession) {
+ mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0;
+ if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) {
// Reset the session state
resetSessionLocked();
}
- if (resetClient) {
+ if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) {
// Reset connection to system
mServiceClient = null;
}
}
+ sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0;
+ sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0;
}
/**
@@ -1437,7 +1473,9 @@
* Marks the state of the session as finished.
*
* @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null}
- * FillResponse) or {@link #STATE_UNKNOWN} (because the session was removed).
+ * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), or
+ * {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service disabled further autofill
+ * requests for the activity).
*/
private void setSessionFinished(int newState) {
synchronized (mLock) {
@@ -1482,10 +1520,10 @@
}
}
- private void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
+ private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
if (sVerbose) {
Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
- + ", finished=" + sessionFinished);
+ + ", sessionFinishedState=" + sessionFinishedState);
}
final View anchor = findView(id);
if (anchor == null) {
@@ -1508,9 +1546,9 @@
}
}
- if (sessionFinished) {
+ if (sessionFinishedState != 0) {
// Callback call was "hijacked" to also update the session state.
- setSessionFinished(STATE_FINISHED);
+ setSessionFinished(sessionFinishedState);
}
}
@@ -1585,6 +1623,7 @@
pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
pw.print(pfx); pw.print("context: "); pw.println(mContext);
+ pw.print(pfx); pw.print("client: "); pw.println(getClientLocked());
pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
@@ -1601,6 +1640,8 @@
pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
+ pw.print(pfx); pw.print("debug: "); pw.print(sDebug);
+ pw.print(" verbose: "); pw.println(sVerbose);
}
private String getStateAsStringLocked() {
@@ -1613,6 +1654,8 @@
return "STATE_FINISHED";
case STATE_SHOWING_SAVE_UI:
return "STATE_SHOWING_SAVE_UI";
+ case STATE_DISABLED_BY_SERVICE:
+ return "STATE_DISABLED_BY_SERVICE";
default:
return "INVALID:" + mState;
}
@@ -1622,8 +1665,8 @@
return mState == STATE_ACTIVE;
}
- private boolean isFinishedLocked() {
- return mState == STATE_FINISHED;
+ private boolean isDisabledByService() {
+ return mState == STATE_DISABLED_BY_SERVICE;
}
private void post(Runnable runnable) {
@@ -1914,10 +1957,10 @@
}
@Override
- public void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+ public void setState(int flags) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() -> afm.setState(enabled, resetSession, resetClient));
+ afm.post(() -> afm.setState(flags));
}
}
@@ -1957,10 +2000,10 @@
}
@Override
- public void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
+ public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinished));
+ afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState));
}
}
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 6bd9bec..9329c4d 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -16,6 +16,7 @@
package android.view.autofill;
+import android.content.ComponentName;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -34,14 +35,15 @@
int addClient(in IAutoFillManagerClient client, int userId);
int startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
- String packageName);
+ in ComponentName componentName);
FillEventHistory getFillEventHistory();
boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback);
void updateSession(int sessionId, in AutofillId id, in Rect bounds,
in AutofillValue value, int action, int flags, int userId);
int updateOrRestartSession(IBinder activityToken, in IBinder appCallback,
in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId,
- boolean hasCallback, int flags, String packageName, int sessionId, int action);
+ boolean hasCallback, int flags, in ComponentName componentName, int sessionId,
+ int action);
void finishSession(int sessionId, int userId);
void cancelSession(int sessionId, int userId);
void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId);
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 56a22c22..254c8a5 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -35,7 +35,7 @@
/**
* Notifies the client when the autofill enabled state changed.
*/
- void setState(boolean enabled, boolean resetSession, boolean resetClient);
+ void setState(int flags);
/**
* Autofills the activity with the contents of a dataset.
@@ -68,9 +68,10 @@
void requestHideFillUi(int sessionId, in AutofillId id);
/**
- * Notifies no fill UI will be shown, and also mark the state as finished if necessary.
+ * Notifies no fill UI will be shown, and also mark the state as finished if necessary (if
+ * sessionFinishedState != 0).
*/
- void notifyNoFillUi(int sessionId, in AutofillId id, boolean sessionFinished);
+ void notifyNoFillUi(int sessionId, in AutofillId id, int sessionFinishedState);
/**
* Starts the provided intent sender.
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 8c3b8a2..26d2141 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -33,6 +33,52 @@
/**
* Information for generating a widget to handle classified text.
+ *
+ * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may
+ * be used to build a widget that can be used to act on classified text.
+ *
+ * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app:
+ *
+ * <pre>{@code
+ * // Called preferably outside the UiThread.
+ * TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *
+ * // Called on the UiThread.
+ * Button button = new Button(context);
+ * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
+ * button.setText(classification.getLabel());
+ * button.setOnClickListener(classification.getOnClickListener());
+ * }</pre>
+ *
+ * <p>e.g. starting an action mode with menu items that can handle the classified text:
+ *
+ * <pre>{@code
+ * // Called preferably outside the UiThread.
+ * final TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *
+ * // Called on the UiThread.
+ * view.startActionMode(new ActionMode.Callback() {
+ *
+ * public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ * for (int i = 0; i < classification.getActionCount(); i++) {
+ * if (thisAppHasPermissionToInvokeIntent(classification.getIntent(i))) {
+ * menu.add(Menu.NONE, i, 20, classification.getLabel(i))
+ * .setIcon(classification.getIcon(i))
+ * .setIntent(classification.getIntent(i));
+ * }
+ * }
+ * return true;
+ * }
+ *
+ * public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ * context.startActivity(item.getIntent());
+ * return true;
+ * }
+ *
+ * ...
+ * });
+ * }</pre>
+ *
*/
public final class TextClassification {
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index c3601d9..46dbd0e 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -29,8 +29,8 @@
/**
* Interface for providing text classification related features.
*
- * <p>Unless otherwise stated, methods of this interface are blocking operations and you should
- * avoid calling them on the UI thread.
+ * <p>Unless otherwise stated, methods of this interface are blocking operations.
+ * Avoid calling them on the UI thread.
*/
public interface TextClassifier {
@@ -75,8 +75,8 @@
};
/**
- * Returns suggested text selection indices, recognized types and their associated confidence
- * scores. The selections are ordered from highest to lowest scoring.
+ * Returns suggested text selection start and end indices, recognized entity types, and their
+ * associated confidence scores. The entity types are ordered from highest to lowest scoring.
*
* @param text text providing context for the selected text (which is specified
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index 39874e8..3861695 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -37,7 +37,7 @@
}
/**
- * Returns the file extension or an empty string iff there is no
+ * Returns the file extension or an empty string if there is no
* extension. This method is a convenience method for obtaining the
* extension of a url and has undefined results for other Strings.
* @param url
@@ -76,7 +76,7 @@
/**
* Return {@code true} if the given MIME type has an entry in the map.
* @param mimeType A MIME type (i.e. text/plain)
- * @return {@code true} iff there is a mimeType entry in the map.
+ * @return {@code true} if there is a mimeType entry in the map.
*/
public boolean hasMimeType(String mimeType) {
return MimeUtils.hasMimeType(mimeType);
@@ -85,7 +85,7 @@
/**
* Return the MIME type for the given extension.
* @param extension A file extension without the leading '.'
- * @return The MIME type for the given extension or {@code null} iff there is none.
+ * @return The MIME type for the given extension or {@code null} if there is none.
*/
@Nullable
public String getMimeTypeFromExtension(String extension) {
@@ -100,7 +100,7 @@
/**
* Return {@code true} if the given extension has a registered MIME type.
* @param extension A file extension without the leading '.'
- * @return {@code true} iff there is an extension entry in the map.
+ * @return {@code true} if there is an extension entry in the map.
*/
public boolean hasExtension(String extension) {
return MimeUtils.hasExtension(extension);
@@ -111,7 +111,7 @@
* MIME types map to multiple extensions. This call will return the most
* common extension for the given MIME type.
* @param mimeType A MIME type (i.e. text/plain)
- * @return The extension for the given MIME type or {@code null} iff there is none.
+ * @return The extension for the given MIME type or {@code null} if there is none.
*/
@Nullable
public String getExtensionFromMimeType(String mimeType) {
diff --git a/core/java/android/webkit/ServiceWorkerClient.java b/core/java/android/webkit/ServiceWorkerClient.java
index d6e8f36..9124c85 100644
--- a/core/java/android/webkit/ServiceWorkerClient.java
+++ b/core/java/android/webkit/ServiceWorkerClient.java
@@ -29,9 +29,9 @@
* application to return the data. If the return value is {@code null}, the
* Service Worker will continue to load the resource as usual.
* Otherwise, the return response and data will be used.
- * NOTE: This method is called on a thread other than the UI thread
- * so clients should exercise caution when accessing private data
- * or the view system.
+ *
+ * <p class="note"><b>Note:</b> This method is called on a thread other than the UI thread so
+ * clients should exercise caution when accessing private data or the view system.
*
* @param request Object containing the details of the request.
* @return A {@link android.webkit.WebResourceResponse} containing the
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index c2f121a..84c000a 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -137,7 +137,7 @@
}
/**
- * @return {@code true} iff the url is correctly URL encoded
+ * @return {@code true} if the url is correctly URL encoded
*/
static boolean verifyURLEncoding(String url) {
int count = url.length();
@@ -171,14 +171,14 @@
}
/**
- * @return {@code true} iff the url is an asset file.
+ * @return {@code true} if the url is an asset file.
*/
public static boolean isAssetUrl(String url) {
return (null != url) && url.startsWith(ASSET_BASE);
}
/**
- * @return {@code true} iff the url is a resource file.
+ * @return {@code true} if the url is a resource file.
* @hide
*/
public static boolean isResourceUrl(String url) {
@@ -186,7 +186,7 @@
}
/**
- * @return {@code true} iff the url is a proxy url to allow cookieless network
+ * @return {@code true} if the url is a proxy url to allow cookieless network
* requests from a file url.
* @deprecated Cookieless proxy is no longer supported.
*/
@@ -196,7 +196,7 @@
}
/**
- * @return {@code true} iff the url is a local file.
+ * @return {@code true} if the url is a local file.
*/
public static boolean isFileUrl(String url) {
return (null != url) && (url.startsWith(FILE_BASE) &&
@@ -205,28 +205,28 @@
}
/**
- * @return {@code true} iff the url is an about: url.
+ * @return {@code true} if the url is an about: url.
*/
public static boolean isAboutUrl(String url) {
return (null != url) && url.startsWith("about:");
}
/**
- * @return {@code true} iff the url is a data: url.
+ * @return {@code true} if the url is a data: url.
*/
public static boolean isDataUrl(String url) {
return (null != url) && url.startsWith("data:");
}
/**
- * @return {@code true} iff the url is a javascript: url.
+ * @return {@code true} if the url is a javascript: url.
*/
public static boolean isJavaScriptUrl(String url) {
return (null != url) && url.startsWith("javascript:");
}
/**
- * @return {@code true} iff the url is an http: url.
+ * @return {@code true} if the url is an http: url.
*/
public static boolean isHttpUrl(String url) {
return (null != url) &&
@@ -235,7 +235,7 @@
}
/**
- * @return {@code true} iff the url is an https: url.
+ * @return {@code true} if the url is an https: url.
*/
public static boolean isHttpsUrl(String url) {
return (null != url) &&
@@ -244,7 +244,7 @@
}
/**
- * @return {@code true} iff the url is a network url.
+ * @return {@code true} if the url is a network url.
*/
public static boolean isNetworkUrl(String url) {
if (url == null || url.length() == 0) {
@@ -254,14 +254,14 @@
}
/**
- * @return {@code true} iff the url is a content: url.
+ * @return {@code true} if the url is a content: url.
*/
public static boolean isContentUrl(String url) {
return (null != url) && url.startsWith(CONTENT_BASE);
}
/**
- * @return {@code true} iff the url is valid.
+ * @return {@code true} if the url is valid.
*/
public static boolean isValidUrl(String url) {
if (url == null || url.length() == 0) {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index dfc81b2..9295f5c 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -191,7 +191,7 @@
* {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
* (introduced in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}).
*
- * <p>NOTE: Using zoom if either the height or width is set to
+ * <p class="note"><b>Note:</b> Using zoom if either the height or width is set to
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior
* and should be avoided.
*
@@ -536,9 +536,13 @@
}
/**
- * Constructs a new WebView with a Context object.
+ * Constructs a new WebView with an Activity Context object.
*
- * @param context a Context object used to access application assets
+ * <p class="note"><b>Note:</b> WebView should always be instantiated with an Activity Context.
+ * If instantiated with an Application Context, WebView will be unable to provide several
+ * features, such as JavaScript dialogs and autofill.
+ *
+ * @param context an Activity Context to access application assets
*/
public WebView(Context context) {
this(context, null);
@@ -547,7 +551,7 @@
/**
* Constructs a new WebView with layout parameters.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
*/
public WebView(Context context, AttributeSet attrs) {
@@ -557,7 +561,7 @@
/**
* Constructs a new WebView with layout parameters and a default style.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
@@ -570,7 +574,7 @@
/**
* Constructs a new WebView with layout parameters and a default style.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
@@ -587,7 +591,7 @@
/**
* Constructs a new WebView with layout parameters and a default style.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
@@ -612,7 +616,7 @@
* time. This guarantees that these interfaces will be available when the JS
* context is initialized.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
@@ -1103,7 +1107,7 @@
/**
* Gets whether this WebView has a back history item.
*
- * @return {@code true} iff this WebView has a back history item
+ * @return {@code true} if this WebView has a back history item
*/
public boolean canGoBack() {
checkThread();
@@ -1121,7 +1125,7 @@
/**
* Gets whether this WebView has a forward history item.
*
- * @return {@code true} iff this WebView has a forward history item
+ * @return {@code true} if this WebView has a forward history item
*/
public boolean canGoForward() {
checkThread();
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index c5b64eb..517ad07 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -130,7 +130,7 @@
* <p>This method is called when the body of the HTTP response has started loading, is reflected
* in the DOM, and will be visible in subsequent draws. This callback occurs early in the
* document loading process, and as such you should expect that linked resources (for example,
- * css and images) may not be available.
+ * CSS and images) may not be available.
*
* <p>For more fine-grained notification of visual state updates, see {@link
* WebView#postVisualStateCallback}.
@@ -150,13 +150,15 @@
* Notify the host application of a resource request and allow the
* application to return the data. If the return value is {@code null}, the WebView
* will continue to load the resource as usual. Otherwise, the return
- * response and data will be used. NOTE: This method is called on a thread
+ * response and data will be used.
+ *
+ * <p class="note"><b>Note:</b> This method is called on a thread
* other than the UI thread so clients should exercise caution
* when accessing private data or the view system.
*
- * <p>Note: when Safe Browsing is enabled, these URLs still undergo Safe Browsing checks. If
- * this is undesired, whitelist the URL with {@link WebView#setSafeBrowsingWhitelist} or ignore
- * the warning with {@link #onSafeBrowsingHit}.
+ * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
+ * Browsing checks. If this is undesired, whitelist the URL with {@link
+ * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}.
*
* @param view The {@link android.webkit.WebView} that is requesting the
* resource.
@@ -178,13 +180,15 @@
* Notify the host application of a resource request and allow the
* application to return the data. If the return value is {@code null}, the WebView
* will continue to load the resource as usual. Otherwise, the return
- * response and data will be used. NOTE: This method is called on a thread
+ * response and data will be used.
+ *
+ * <p class="note"><b>Note:</b> This method is called on a thread
* other than the UI thread so clients should exercise caution
* when accessing private data or the view system.
*
- * <p>Note: when Safe Browsing is enabled, these URLs still undergo Safe Browsing checks. If
- * this is undesired, whitelist the URL with {@link WebView#setSafeBrowsingWhitelist} or ignore
- * the warning with {@link #onSafeBrowsingHit}.
+ * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
+ * Browsing checks. If this is undesired, whitelist the URL with {@link
+ * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}.
*
* @param view The {@link android.webkit.WebView} that is requesting the
* resource.
@@ -248,7 +252,7 @@
public static final int ERROR_FILE_NOT_FOUND = -14;
/** Too many requests during this load */
public static final int ERROR_TOO_MANY_REQUESTS = -15;
- /** Resource load was cancelled by Safe Browsing */
+ /** Resource load was canceled by Safe Browsing */
public static final int ERROR_UNSAFE_RESOURCE = -16;
/** @hide */
@@ -272,8 +276,8 @@
/**
* Report an error to the host application. These errors are unrecoverable
- * (i.e. the main resource is unavailable). The errorCode parameter
- * corresponds to one of the ERROR_* constants.
+ * (i.e. the main resource is unavailable). The {@code errorCode} parameter
+ * corresponds to one of the {@code ERROR_*} constants.
* @param view The WebView that is initiating the callback.
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
@@ -289,11 +293,11 @@
/**
* Report web resource loading error to the host application. These errors usually indicate
* inability to connect to the server. Note that unlike the deprecated version of the callback,
- * the new version will be called for any resource (iframe, image, etc), not just for the main
+ * the new version will be called for any resource (iframe, image, etc.), not just for the main
* page. Thus, it is recommended to perform minimum required work in this callback.
* @param view The WebView that is initiating the callback.
* @param request The originating request.
- * @param error Information about the error occured.
+ * @param error Information about the error occurred.
*/
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
if (request.isForMainFrame()) {
@@ -306,12 +310,12 @@
/**
* Notify the host application that an HTTP error has been received from the server while
* loading a resource. HTTP errors have status codes >= 400. This callback will be called
- * for any resource (iframe, image, etc), not just for the main page. Thus, it is recommended to
- * perform minimum required work in this callback. Note that the content of the server
- * response may not be provided within the <b>errorResponse</b> parameter.
+ * for any resource (iframe, image, etc.), not just for the main page. Thus, it is recommended
+ * to perform minimum required work in this callback. Note that the content of the server
+ * response may not be provided within the {@code errorResponse} parameter.
* @param view The WebView that is initiating the callback.
* @param request The originating request.
- * @param errorResponse Information about the error occured.
+ * @param errorResponse Information about the error occurred.
*/
public void onReceivedHttpError(
WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
@@ -365,7 +369,7 @@
* if desired and providing the keys. There are three ways to
* respond: proceed(), cancel() or ignore(). Webview stores the response
* in memory (for the life of the application) if proceed() or cancel() is
- * called and does not call onReceivedClientCertRequest() again for the
+ * called and does not call {@code onReceivedClientCertRequest()} again for the
* same host and port pair. Webview does not store the response if ignore()
* is called. Note that, multiple layers in chromium network stack might be
* caching the responses, so the behavior for ignore is only a best case
@@ -432,7 +436,7 @@
/**
* Notify the host application that a key was not handled by the WebView.
* Except system keys, WebView always consumes the keys in the normal flow
- * or if shouldOverrideKeyEvent returns {@code true}. This is called asynchronously
+ * or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
* from where the key is dispatched. It gives the host application a chance
* to handle the unhandled key events.
*
@@ -446,7 +450,7 @@
/**
* Notify the host application that a input event was not handled by the WebView.
* Except system keys, WebView always consumes input events in the normal flow
- * or if shouldOverrideKeyEvent returns {@code true}. This is called asynchronously
+ * or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
* from where the event is dispatched. It gives the host application a chance
* to handle the unhandled input events.
*
@@ -503,7 +507,7 @@
}
/**
- * Notify host application that the given webview's render process has exited.
+ * Notify host application that the given WebView's render process has exited.
*
* Multiple WebView instances may be associated with a single render process;
* onRenderProcessGone will be called for each WebView that was affected.
@@ -513,10 +517,10 @@
*
* The given WebView can't be used, and should be removed from the view hierarchy,
* all references to it should be cleaned up, e.g any references in the Activity
- * or other classes saved using findViewById and similar calls, etc
+ * or other classes saved using {@link android.view.View#findViewById} and similar calls, etc.
*
* To cause an render process crash for test purpose, the application can
- * call loadUrl("chrome://crash") on the WebView. Note that multiple WebView
+ * call {@code loadUrl("chrome://crash")} on the WebView. Note that multiple WebView
* instances may be affected if they share a render process, not just the
* specific WebView which loaded chrome://crash.
*
@@ -537,12 +541,13 @@
* behavior is to show an interstitial to the user, with the reporting checkbox visible.
*
* If the application needs to show its own custom interstitial UI, the callback can be invoked
- * asynchronously with backToSafety() or proceed(), depending on user response.
+ * asynchronously with {@link SafeBrowsingResponse#backToSafety} or {@link
+ * SafeBrowsingResponse#proceed}, depending on user response.
*
* @param view The WebView that hit the malicious resource.
* @param request Object containing the details of the request.
* @param threatType The reason the resource was caught by Safe Browsing, corresponding to a
- * SAFE_BROWSING_THREAT_* value.
+ * {@code SAFE_BROWSING_THREAT_*} value.
* @param callback Applications must invoke one of the callback methods.
*/
public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 95cb454..797bdfb 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -25,7 +25,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
-import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
@@ -138,7 +137,7 @@
}
/**
- * Load the native library for the given package name iff that package
+ * Load the native library for the given package name if that package
* name is the same as the one providing the webview.
*/
public static int loadWebViewNativeLibraryFromPackage(String packageName,
@@ -445,38 +444,17 @@
}
}
- private static int prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
- if (DEBUG) Log.v(LOGTAG, "creating relro files");
- int numRelros = 0;
-
- // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
- // unexpected values will be handled there to ensure that we trigger notifying any process
- // waiting on relro creation.
- if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
- if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
- WebViewLibraryLoader.createRelroFile(false /* is64Bit */, nativeLibraryPaths[0]);
- numRelros++;
- }
-
- if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
- if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
- WebViewLibraryLoader.createRelroFile(true /* is64Bit */, nativeLibraryPaths[1]);
- numRelros++;
- }
- return numRelros;
- }
-
/**
* @hide
*/
public static int onWebViewProviderChanged(PackageInfo packageInfo) {
- String[] nativeLibs = null;
+ int startedRelroProcesses = 0;
ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo);
try {
fixupStubApplicationInfo(packageInfo.applicationInfo,
AppGlobals.getInitialApplication().getPackageManager());
- nativeLibs = WebViewLibraryLoader.updateWebViewZygoteVmSize(packageInfo);
+ startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
} catch (Throwable t) {
// Log and discard errors at this stage as we must not crash the system server.
Log.e(LOGTAG, "error preparing webview native library", t);
@@ -484,7 +462,7 @@
WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo);
- return prepareWebViewInSystemServer(nativeLibs);
+ return startedRelroProcesses;
}
private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate";
diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java
index fa1a390..de0b97d 100644
--- a/core/java/android/webkit/WebViewLibraryLoader.java
+++ b/core/java/android/webkit/WebViewLibraryLoader.java
@@ -16,6 +16,8 @@
package android.webkit;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -26,6 +28,7 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import dalvik.system.VMRuntime;
@@ -36,7 +39,11 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-class WebViewLibraryLoader {
+/**
+ * @hide
+ */
+@VisibleForTesting
+public class WebViewLibraryLoader {
private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName();
private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
@@ -94,7 +101,7 @@
/**
* Create a single relro file by invoking an isolated process that to do the actual work.
*/
- static void createRelroFile(final boolean is64Bit, String nativeLibraryPath) {
+ static void createRelroFile(final boolean is64Bit, @NonNull WebViewNativeLibrary nativeLib) {
final String abi =
is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
@@ -112,12 +119,12 @@
};
try {
- if (nativeLibraryPath == null) {
+ if (nativeLib == null || nativeLib.path == null) {
throw new IllegalArgumentException(
"Native library paths to the WebView RelRo process must not be null!");
}
int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
- RelroFileCreator.class.getName(), new String[] { nativeLibraryPath },
+ RelroFileCreator.class.getName(), new String[] { nativeLib.path },
"WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
} catch (Throwable t) {
@@ -128,56 +135,77 @@
}
/**
+ * Perform preparations needed to allow loading WebView from an application. This method should
+ * be called whenever we change WebView provider.
+ * @return the number of relro processes started.
+ */
+ static int prepareNativeLibraries(PackageInfo webviewPackageInfo)
+ throws WebViewFactory.MissingWebViewPackageException {
+ WebViewNativeLibrary nativeLib32bit =
+ getWebViewNativeLibrary(webviewPackageInfo, false /* is64bit */);
+ WebViewNativeLibrary nativeLib64bit =
+ getWebViewNativeLibrary(webviewPackageInfo, true /* is64bit */);
+ updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit);
+
+ return createRelros(nativeLib32bit, nativeLib64bit);
+ }
+
+ /**
+ * @return the number of relro processes started.
+ */
+ private static int createRelros(@Nullable WebViewNativeLibrary nativeLib32bit,
+ @Nullable WebViewNativeLibrary nativeLib64bit) {
+ if (DEBUG) Log.v(LOGTAG, "creating relro files");
+ int numRelros = 0;
+
+ if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
+ if (nativeLib32bit == null) {
+ Log.e(LOGTAG, "No 32-bit WebView library path, skipping relro creation.");
+ } else {
+ if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
+ createRelroFile(false /* is64Bit */, nativeLib32bit);
+ numRelros++;
+ }
+ }
+
+ if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
+ if (nativeLib64bit == null) {
+ Log.e(LOGTAG, "No 64-bit WebView library path, skipping relro creation.");
+ } else {
+ if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
+ createRelroFile(true /* is64Bit */, nativeLib64bit);
+ numRelros++;
+ }
+ }
+ return numRelros;
+ }
+
+ /**
*
* @return the native WebView libraries in the new WebView APK.
*/
- static String[] updateWebViewZygoteVmSize(PackageInfo packageInfo)
+ private static void updateWebViewZygoteVmSize(
+ @Nullable WebViewNativeLibrary nativeLib32bit,
+ @Nullable WebViewNativeLibrary nativeLib64bit)
throws WebViewFactory.MissingWebViewPackageException {
// Find the native libraries of the new WebView package, to change the size of the
// memory region in the Zygote reserved for the library.
- String[] nativeLibs = getWebViewNativeLibraryPaths(packageInfo);
- if (nativeLibs != null) {
- long newVmSize = 0L;
+ long newVmSize = 0L;
- for (String path : nativeLibs) {
- if (path == null || TextUtils.isEmpty(path)) continue;
- if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
- File f = new File(path);
- if (f.exists()) {
- newVmSize = Math.max(newVmSize, f.length());
- continue;
- }
- if (path.contains("!/")) {
- String[] split = TextUtils.split(path, "!/");
- if (split.length == 2) {
- try (ZipFile z = new ZipFile(split[0])) {
- ZipEntry e = z.getEntry(split[1]);
- if (e != null && e.getMethod() == ZipEntry.STORED) {
- newVmSize = Math.max(newVmSize, e.getSize());
- continue;
- }
- }
- catch (IOException e) {
- Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e);
- }
- }
- }
- Log.e(LOGTAG, "error sizing load for " + path);
- }
+ if (nativeLib32bit != null) newVmSize = Math.max(newVmSize, nativeLib32bit.size);
+ if (nativeLib64bit != null) newVmSize = Math.max(newVmSize, nativeLib64bit.size);
- if (DEBUG) {
- Log.v(LOGTAG, "Based on library size, need " + newVmSize
- + " bytes of address space.");
- }
- // The required memory can be larger than the file on disk (due to .bss), and an
- // upgraded version of the library will likely be larger, so always attempt to
- // reserve twice as much as we think to allow for the library to grow during this
- // boot cycle.
- newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
- Log.d(LOGTAG, "Setting new address space to " + newVmSize);
- setWebViewZygoteVmSize(newVmSize);
+ if (DEBUG) {
+ Log.v(LOGTAG, "Based on library size, need " + newVmSize
+ + " bytes of address space.");
}
- return nativeLibs;
+ // The required memory can be larger than the file on disk (due to .bss), and an
+ // upgraded version of the library will likely be larger, so always attempt to
+ // reserve twice as much as we think to allow for the library to grow during this
+ // boot cycle.
+ newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
+ Log.d(LOGTAG, "Setting new address space to " + newVmSize);
+ setWebViewZygoteVmSize(newVmSize);
}
/**
@@ -201,7 +229,9 @@
/**
* Load WebView's native library into the current process.
- * Note: assumes that we have waited for relro creation.
+ *
+ * <p class="note"><b>Note:</b> Assumes that we have waited for relro creation.
+ *
* @param clazzLoader class loader used to find the linker namespace to load the library into.
* @param packageInfo the package from which WebView is loaded.
*/
@@ -227,64 +257,78 @@
/**
* Fetch WebView's native library paths from {@param packageInfo}.
+ * @hide
*/
- static String[] getWebViewNativeLibraryPaths(PackageInfo packageInfo)
- throws WebViewFactory.MissingWebViewPackageException {
+ @Nullable
+ @VisibleForTesting
+ public static WebViewNativeLibrary getWebViewNativeLibrary(PackageInfo packageInfo,
+ boolean is64bit) throws WebViewFactory.MissingWebViewPackageException {
ApplicationInfo ai = packageInfo.applicationInfo;
final String nativeLibFileName = WebViewFactory.getWebViewLibrary(ai);
- String path32;
- String path64;
- boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
- if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
- // Multi-arch case.
- if (primaryArchIs64bit) {
- // Primary arch: 64-bit, secondary: 32-bit.
- path64 = ai.nativeLibraryDir;
- path32 = ai.secondaryNativeLibraryDir;
- } else {
- // Primary arch: 32-bit, secondary: 64-bit.
- path64 = ai.secondaryNativeLibraryDir;
- path32 = ai.nativeLibraryDir;
- }
- } else if (primaryArchIs64bit) {
- // Single-arch 64-bit.
- path64 = ai.nativeLibraryDir;
- path32 = "";
- } else {
- // Single-arch 32-bit.
- path32 = ai.nativeLibraryDir;
- path64 = "";
- }
+ String dir = getWebViewNativeLibraryDirectory(ai, is64bit /* 64bit */);
- // Form the full paths to the extracted native libraries.
- // If libraries were not extracted, try load from APK paths instead.
- if (!TextUtils.isEmpty(path32)) {
- path32 += "/" + nativeLibFileName;
- File f = new File(path32);
- if (!f.exists()) {
- path32 = getLoadFromApkPath(ai.sourceDir,
- Build.SUPPORTED_32_BIT_ABIS,
- nativeLibFileName);
- }
- }
- if (!TextUtils.isEmpty(path64)) {
- path64 += "/" + nativeLibFileName;
- File f = new File(path64);
- if (!f.exists()) {
- path64 = getLoadFromApkPath(ai.sourceDir,
- Build.SUPPORTED_64_BIT_ABIS,
- nativeLibFileName);
- }
- }
+ WebViewNativeLibrary lib = findNativeLibrary(ai, nativeLibFileName,
+ is64bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS, dir);
- if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64);
- return new String[] { path32, path64 };
+ if (DEBUG) {
+ Log.v(LOGTAG, String.format("Native %d-bit lib: %s", is64bit ? 64 : 32, lib.path));
+ }
+ return lib;
}
- private static String getLoadFromApkPath(String apkPath,
- String[] abiList,
- String nativeLibFileName)
+ /**
+ * @return the directory of the native WebView library with bitness {@param is64bit}.
+ * @hide
+ */
+ @VisibleForTesting
+ public static String getWebViewNativeLibraryDirectory(ApplicationInfo ai, boolean is64bit) {
+ // Primary arch has the same bitness as the library we are looking for.
+ if (is64bit == VMRuntime.is64BitAbi(ai.primaryCpuAbi)) return ai.nativeLibraryDir;
+
+ // Secondary arch has the same bitness as the library we are looking for.
+ if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
+ return ai.secondaryNativeLibraryDir;
+ }
+
+ return "";
+ }
+
+ /**
+ * @return an object describing a native WebView library given the directory path of that
+ * library, or null if the library couldn't be found.
+ */
+ @Nullable
+ private static WebViewNativeLibrary findNativeLibrary(ApplicationInfo ai,
+ String nativeLibFileName, String[] abiList, String libDirectory)
+ throws WebViewFactory.MissingWebViewPackageException {
+ if (TextUtils.isEmpty(libDirectory)) return null;
+ String libPath = libDirectory + "/" + nativeLibFileName;
+ File f = new File(libPath);
+ if (f.exists()) {
+ return new WebViewNativeLibrary(libPath, f.length());
+ } else {
+ return getLoadFromApkPath(ai.sourceDir, abiList, nativeLibFileName);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public static class WebViewNativeLibrary {
+ public final String path;
+ public final long size;
+
+ WebViewNativeLibrary(String path, long size) {
+ this.path = path;
+ this.size = size;
+ }
+ }
+
+ private static WebViewNativeLibrary getLoadFromApkPath(String apkPath,
+ String[] abiList,
+ String nativeLibFileName)
throws WebViewFactory.MissingWebViewPackageException {
// Search the APK for a native library conforming to a listed ABI.
try (ZipFile z = new ZipFile(apkPath)) {
@@ -293,13 +337,13 @@
ZipEntry e = z.getEntry(entry);
if (e != null && e.getMethod() == ZipEntry.STORED) {
// Return a path formatted for dlopen() load from APK.
- return apkPath + "!/" + entry;
+ return new WebViewNativeLibrary(apkPath + "!/" + entry, e.getSize());
}
}
} catch (IOException e) {
throw new WebViewFactory.MissingWebViewPackageException(e);
}
- return "";
+ return null;
}
/**
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index c46c681..a896925 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -316,7 +316,7 @@
/**
* Provides mechanism for the name-sake methods declared in View and ViewGroup to be delegated
* into the WebViewProvider instance.
- * NOTE For many of these methods, the WebView will provide a super.Foo() call before or after
+ * NOTE: For many of these methods, the WebView will provide a super.Foo() call before or after
* making the call into the provider instance. This is done for convenience in the common case
* of maintaining backward compatibility. For remaining super class calls (e.g. where the
* provider may need to only conditionally make the call based on some internal state) see the
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index dd01251..6c19256 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -202,7 +202,7 @@
* The last selected position we used when notifying
*/
int mOldSelectedPosition = INVALID_POSITION;
-
+
/**
* The id of the last selected position we used when notifying
*/
@@ -382,7 +382,7 @@
* position is different from the previously selected position or if
* there was no selected item.</p>
*
- * Impelmenters can call getItemAtPosition(position) if they need to access the
+ * Implementers can call getItemAtPosition(position) if they need to access the
* data associated with the selected item.
*
* @param parent The AdapterView where the selection happened
@@ -778,8 +778,8 @@
// We are now GONE, so pending layouts will not be dispatched.
// Force one here to make sure that the state of the list matches
// the state of the adapter.
- if (mDataChanged) {
- this.onLayout(false, mLeft, mTop, mRight, mBottom);
+ if (mDataChanged) {
+ this.onLayout(false, mLeft, mTop, mRight, mBottom);
}
} else {
if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
@@ -1304,4 +1304,4 @@
structure.setAutofillOptions(options);
}
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 384f4f8..e6da69d 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -129,7 +129,7 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
-
+import java.util.Map;
/**
* Helper class used by TextView to handle editable text views.
@@ -163,9 +163,9 @@
private static final int MENU_ITEM_ORDER_REPLACE = 9;
private static final int MENU_ITEM_ORDER_AUTOFILL = 10;
private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
+ private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
- private static final float MAGNIFIER_ZOOM = 1.25f;
@IntDef({MagnifierHandleTrigger.SELECTION_START,
MagnifierHandleTrigger.SELECTION_END,
MagnifierHandleTrigger.INSERTION})
@@ -3793,6 +3793,7 @@
private final RectF mSelectionBounds = new RectF();
private final boolean mHasSelection;
private final int mHandleHeight;
+ private final Map<MenuItem, OnClickListener> mAssistClickHandlers = new HashMap<>();
public TextActionModeCallback(boolean hasSelection) {
mHasSelection = hasSelection;
@@ -3820,6 +3821,8 @@
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ mAssistClickHandlers.clear();
+
mode.setTitle(null);
mode.setSubtitle(null);
mode.setTitleOptionalHint(true);
@@ -3903,14 +3906,14 @@
updateSelectAllItem(menu);
updateReplaceItem(menu);
- updateAssistMenuItem(menu);
+ updateAssistMenuItems(menu);
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
updateSelectAllItem(menu);
updateReplaceItem(menu);
- updateAssistMenuItem(menu);
+ updateAssistMenuItems(menu);
Callback customCallback = getCustomCallback();
if (customCallback != null) {
@@ -3943,32 +3946,118 @@
}
}
- private void updateAssistMenuItem(Menu menu) {
- menu.removeItem(TextView.ID_ASSIST);
+ private void updateAssistMenuItems(Menu menu) {
+ clearAssistMenuItems(menu);
+ if (!mTextView.isDeviceProvisioned()) {
+ return;
+ }
final TextClassification textClassification =
getSelectionActionModeHelper().getTextClassification();
- if (canAssist()) {
- menu.add(TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
- textClassification.getLabel())
- .setIcon(textClassification.getIcon())
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- mMetricsLogger.write(
- new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
- .setType(MetricsEvent.TYPE_OPEN)
- .setSubtype(textClassification.getLogType()));
+ final int count = textClassification != null ? textClassification.getActionCount() : 0;
+ for (int i = 0; i < count; i++) {
+ if (!isValidAssistMenuItem(i)) {
+ continue;
+ }
+ final int groupId = TextView.ID_ASSIST;
+ final int order = (i == 0)
+ ? MENU_ITEM_ORDER_ASSIST
+ : MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i;
+ final int id = (i == 0) ? TextView.ID_ASSIST : Menu.NONE;
+ final int showAsFlag = (i == 0)
+ ? MenuItem.SHOW_AS_ACTION_ALWAYS
+ : MenuItem.SHOW_AS_ACTION_NEVER;
+ final MenuItem item = menu.add(
+ groupId, id, order, textClassification.getLabel(i))
+ .setIcon(textClassification.getIcon(i))
+ .setIntent(textClassification.getIntent(i));
+ item.setShowAsAction(showAsFlag);
+ mAssistClickHandlers.put(item, textClassification.getOnClickListener(i));
+ if (id == TextView.ID_ASSIST) {
+ mMetricsLogger.write(
+ new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
+ .setType(MetricsEvent.TYPE_OPEN)
+ .setSubtype(textClassification.getLogType()));
+ }
}
}
- private boolean canAssist() {
+ private void clearAssistMenuItems(Menu menu) {
+ int i = 0;
+ while (i < menu.size()) {
+ final MenuItem menuItem = menu.getItem(i);
+ if (menuItem.getGroupId() == TextView.ID_ASSIST) {
+ menu.removeItem(menuItem.getItemId());
+ continue;
+ }
+ i++;
+ }
+ }
+
+ private boolean isValidAssistMenuItem(int index) {
final TextClassification textClassification =
getSelectionActionModeHelper().getTextClassification();
- return mTextView.isDeviceProvisioned()
- && textClassification != null
- && (textClassification.getIcon() != null
- || !TextUtils.isEmpty(textClassification.getLabel()))
- && (textClassification.getOnClickListener() != null
- || (textClassification.getIntent() != null
- && mTextView.getContext().canStartActivityForResult()));
+ if (!mTextView.isDeviceProvisioned() || textClassification == null
+ || index < 0 || index >= textClassification.getActionCount()) {
+ return false;
+ }
+ final Drawable icon = textClassification.getIcon(index);
+ final CharSequence label = textClassification.getLabel(index);
+ final boolean hasUi = icon != null || !TextUtils.isEmpty(label);
+ final OnClickListener onClick = textClassification.getOnClickListener(index);
+ final Intent intent = textClassification.getIntent(index);
+ final boolean hasAction = onClick != null || isSupportedIntent(intent);
+ return hasUi && hasAction;
+ }
+
+ private boolean isSupportedIntent(Intent intent) {
+ if (intent == null) {
+ return false;
+ }
+ final Context context = mTextView.getContext();
+ final ResolveInfo info = context.getPackageManager().resolveActivity(intent, 0);
+ final boolean samePackage = context.getPackageName().equals(
+ info.activityInfo.packageName);
+ if (samePackage) {
+ return true;
+ }
+
+ final boolean exported = info.activityInfo.exported;
+ final boolean requiresPermission = info.activityInfo.permission != null;
+ final boolean hasPermission = !requiresPermission
+ || context.checkSelfPermission(info.activityInfo.permission)
+ == PackageManager.PERMISSION_GRANTED;
+ return exported && hasPermission;
+ }
+
+ private boolean onAssistMenuItemClicked(MenuItem assistMenuItem) {
+ Preconditions.checkArgument(assistMenuItem.getGroupId() == TextView.ID_ASSIST);
+
+ final TextClassification textClassification =
+ getSelectionActionModeHelper().getTextClassification();
+ if (!mTextView.isDeviceProvisioned() || textClassification == null) {
+ // No textClassification result to handle the click. Eat the click.
+ return true;
+ }
+
+ OnClickListener onClickListener = mAssistClickHandlers.get(assistMenuItem);
+ if (onClickListener == null) {
+ final Intent intent = assistMenuItem.getIntent();
+ if (intent != null) {
+ onClickListener = TextClassification.createStartActivityOnClickListener(
+ mTextView.getContext(), intent);
+ }
+ }
+ if (onClickListener != null) {
+ onClickListener.onClick(mTextView);
+ stopTextActionMode();
+ if (assistMenuItem.getItemId() == TextView.ID_ASSIST) {
+ mMetricsLogger.action(
+ MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST,
+ textClassification.getLogType());
+ }
+ }
+ // We tried our best.
+ return true;
}
@Override
@@ -3982,25 +4071,7 @@
if (customCallback != null && customCallback.onActionItemClicked(mode, item)) {
return true;
}
- final TextClassification textClassification =
- getSelectionActionModeHelper().getTextClassification();
- if (TextView.ID_ASSIST == item.getItemId() && textClassification != null) {
- final OnClickListener onClickListener =
- textClassification.getOnClickListener();
- if (onClickListener != null) {
- onClickListener.onClick(mTextView);
- } else {
- final Intent intent = textClassification.getIntent();
- if (intent != null) {
- TextClassification.createStartActivityOnClickListener(
- mTextView.getContext(), intent)
- .onClick(mTextView);
- }
- }
- mMetricsLogger.action(
- MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST,
- textClassification.getLogType());
- stopTextActionMode();
+ if (item.getGroupId() == TextView.ID_ASSIST && onAssistMenuItemClicked(item)) {
return true;
}
return mTextView.onTextContextMenuItem(item.getItemId());
@@ -4029,6 +4100,8 @@
if (mSelectionModifierCursorController != null) {
mSelectionModifierCursorController.hide();
}
+
+ mAssistClickHandlers.clear();
}
@Override
@@ -4547,7 +4620,7 @@
+ mTextView.getTotalPaddingTop() - mTextView.getScrollY();
suspendBlink();
- mMagnifier.show(xPosInView, yPosInView, MAGNIFIER_ZOOM);
+ mMagnifier.show(xPosInView, yPosInView);
}
protected final void dismissMagnifier() {
@@ -6560,6 +6633,9 @@
private void loadSupportedActivities() {
mSupportedActivities.clear();
+ if (!mContext.canStartActivityForResult()) {
+ return;
+ }
PackageManager packageManager = mTextView.getContext().getPackageManager();
List<ResolveInfo> unfiltered =
packageManager.queryIntentActivities(createProcessTextIntent(), 0);
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 0d67615..adf366a 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -657,6 +657,7 @@
mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec,
(heightSpec < 0)? -1 : heightSpec);
+ mPopup.getContentView().restoreDefaultFocus();
} else {
final int widthSpec;
if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
@@ -695,6 +696,7 @@
mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, mDropDownGravity);
mDropDownList.setSelection(ListView.INVALID_POSITION);
+ mPopup.getContentView().restoreDefaultFocus();
if (!mModal || mDropDownList.isInTouchMode()) {
clearListSelection();
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 23ebadb..8dc8cab 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -2461,14 +2461,14 @@
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
enterTransition.addTarget(child);
- child.setVisibility(View.INVISIBLE);
+ child.setTransitionVisibility(View.INVISIBLE);
}
TransitionManager.beginDelayedTransition(this, enterTransition);
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
- child.setVisibility(View.VISIBLE);
+ child.setTransitionVisibility(View.VISIBLE);
}
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 631f388..e3309161 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -81,6 +81,7 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Map;
import java.util.Stack;
import java.util.concurrent.Executor;
@@ -185,6 +186,9 @@
*/
private boolean mIsWidgetCollectionChild = false;
+ /** Class cookies of the Parcel this instance was read from. */
+ private final Map<Class, Object> mClassCookies;
+
private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler();
private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>();
@@ -1505,10 +1509,10 @@
}
ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info,
- int depth) {
+ int depth, Map<Class, Object> classCookies) {
viewId = parcel.readInt();
mIndex = parcel.readInt();
- mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth);
+ mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies);
}
public void writeToParcel(Parcel dest, int flags) {
@@ -2120,6 +2124,7 @@
mApplication = application;
mLayoutId = layoutId;
mBitmapCache = new BitmapCache();
+ mClassCookies = null;
}
private boolean hasLandscapeAndPortraitLayouts() {
@@ -2149,6 +2154,9 @@
mBitmapCache = new BitmapCache();
configureRemoteViewsAsChild(landscape);
configureRemoteViewsAsChild(portrait);
+
+ mClassCookies = (portrait.mClassCookies != null)
+ ? portrait.mClassCookies : landscape.mClassCookies;
}
/**
@@ -2161,15 +2169,16 @@
mLayoutId = src.mLayoutId;
mIsWidgetCollectionChild = src.mIsWidgetCollectionChild;
mReapplyDisallowed = src.mReapplyDisallowed;
+ mClassCookies = src.mClassCookies;
if (src.hasLandscapeAndPortraitLayouts()) {
mLandscape = new RemoteViews(src.mLandscape);
mPortrait = new RemoteViews(src.mPortrait);
-
}
if (src.mActions != null) {
Parcel p = Parcel.obtain();
+ p.putClassCookies(mClassCookies);
src.writeActionsToParcel(p);
p.setDataPosition(0);
// Since src is already in memory, we do not care about stack overflow as it has
@@ -2189,10 +2198,11 @@
* @param parcel
*/
public RemoteViews(Parcel parcel) {
- this(parcel, null, null, 0);
+ this(parcel, null, null, 0, null);
}
- private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth) {
+ private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth,
+ Map<Class, Object> classCookies) {
if (depth > MAX_NESTED_VIEWS
&& (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) {
throw new IllegalArgumentException("Too many nested views.");
@@ -2204,8 +2214,11 @@
// We only store a bitmap cache in the root of the RemoteViews.
if (bitmapCache == null) {
mBitmapCache = new BitmapCache(parcel);
+ // Store the class cookies such that they are available when we clone this RemoteView.
+ mClassCookies = parcel.copyClassCookies();
} else {
setBitmapCache(bitmapCache);
+ mClassCookies = classCookies;
setNotRoot();
}
@@ -2218,8 +2231,9 @@
readActionsFromParcel(parcel, depth);
} else {
// MODE_HAS_LANDSCAPE_AND_PORTRAIT
- mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth);
- mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth);
+ mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies);
+ mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth,
+ mClassCookies);
mApplication = mPortrait.mApplication;
mLayoutId = mPortrait.getLayoutId();
}
@@ -2246,7 +2260,8 @@
case REFLECTION_ACTION_TAG:
return new ReflectionAction(parcel);
case VIEW_GROUP_ACTION_ADD_TAG:
- return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth);
+ return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth,
+ mClassCookies);
case VIEW_GROUP_ACTION_REMOVE_TAG:
return new ViewGroupActionRemove(parcel);
case VIEW_CONTENT_NAVIGATION_TAG:
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
index 5fdc920..15221b1 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -17,7 +17,7 @@
package com.android.internal.app;
// This interface is also used by native code, so must
-// be kept in sync with frameworks/native/include/binder/IAppOpsCallback.h
+// be kept in sync with frameworks/native/libs/binder/include/binder/IAppOpsCallback.h
oneway interface IAppOpsCallback {
void opChanged(int op, int uid, String packageName);
}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 7a119b4..2b975fe 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -22,7 +22,7 @@
interface IAppOpsService {
// These first methods are also called by native code, so must
- // be kept in sync with frameworks/native/include/binder/IAppOpsService.h
+ // be kept in sync with frameworks/native/libs/binder/include/binder/IAppOpsService.h
int checkOperation(int code, int uid, String packageName);
int noteOperation(int code, int uid, String packageName);
int startOperation(IBinder token, int code, int uid, String packageName);
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/java/com/android/internal/app/IAssistDataReceiver.aidl
similarity index 74%
rename from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
rename to core/java/com/android/internal/app/IAssistDataReceiver.aidl
index a987a16..9c9ffef 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/java/com/android/internal/app/IAssistDataReceiver.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,10 @@
package com.android.internal.app;
import android.graphics.Bitmap;
+import android.os.Bundle;
/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
+oneway interface IAssistDataReceiver {
+ void onHandleAssistData(in Bundle resultData);
+ void onHandleAssistScreenshot(in Bitmap screenshot);
}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 4275e0b..f405231 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -29,7 +29,7 @@
interface IBatteryStats {
// These first methods are also called by native code, so must
- // be kept in sync with frameworks/native/include/binder/IBatteryStats.h
+ // be kept in sync with frameworks/native/libs/binder/include/binder/IBatteryStats.h
void noteStartSensor(int uid, int sensor);
void noteStopSensor(int uid, int sensor);
void noteStartVideo(int uid);
diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java
index 386aa84..0a230a9 100644
--- a/core/java/com/android/internal/app/LocaleHelper.java
+++ b/core/java/com/android/internal/app/LocaleHelper.java
@@ -136,7 +136,16 @@
* @return the localized country name.
*/
public static String getDisplayCountry(Locale locale, Locale displayLocale) {
- return ULocale.getDisplayCountry(locale.toLanguageTag(), ULocale.forLocale(displayLocale));
+ final String languageTag = locale.toLanguageTag();
+ final ULocale uDisplayLocale = ULocale.forLocale(displayLocale);
+ final String country = ULocale.getDisplayCountry(languageTag, uDisplayLocale);
+ final String numberingSystem = locale.getUnicodeLocaleType("nu");
+ if (numberingSystem != null) {
+ return String.format("%s (%s)", country,
+ ULocale.getDisplayKeywordValue(languageTag, "numbers", uDisplayLocale));
+ } else {
+ return country;
+ }
}
/**
diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index 9936ed5..c8c2fcf 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -93,10 +93,6 @@
return context.getResources().getStringArray(R.array.supported_locales);
}
- public static String[] getPseudoLocales() {
- return pseudoLocales;
- }
-
public static List<LocaleInfo> getAllAssetLocales(Context context, boolean isInDeveloperMode) {
final Resources resources = context.getResources();
@@ -104,13 +100,6 @@
List<String> localeList = new ArrayList<String>(locales.length);
Collections.addAll(localeList, locales);
- // Don't show the pseudolocales unless we're in developer mode. http://b/17190407.
- if (!isInDeveloperMode) {
- for (String locale : pseudoLocales) {
- localeList.remove(locale);
- }
- }
-
Collections.sort(localeList);
final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);
@@ -122,6 +111,10 @@
|| l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
continue;
}
+ // Don't show the pseudolocales unless we're in developer mode. http://b/17190407.
+ if (!isInDeveloperMode && LocaleList.isPseudoLocale(l)) {
+ continue;
+ }
if (localeInfos.isEmpty()) {
if (DEBUG) {
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index e3fce51..2b0b5ee 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -17,6 +17,7 @@
package com.android.internal.app;
import android.content.Context;
+import android.os.LocaleList;
import android.provider.Settings;
import android.telephony.TelephonyManager;
@@ -68,7 +69,9 @@
return null;
}
return new Locale.Builder()
- .setLocale(locale).setRegion("")
+ .setLocale(locale)
+ .setRegion("")
+ .setExtension(Locale.UNICODE_LOCALE_EXTENSION, "")
.build();
}
@@ -253,11 +256,25 @@
Set<String> simCountries = getSimCountries(context);
+ final boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
for (String localeId : LocalePicker.getSupportedLocales(context)) {
if (localeId.isEmpty()) {
throw new IllformedLocaleException("Bad locale entry in locale_config.xml");
}
LocaleInfo li = new LocaleInfo(localeId);
+
+ if (LocaleList.isPseudoLocale(li.getLocale())) {
+ if (isInDeveloperMode) {
+ li.setTranslated(true);
+ li.mIsPseudo = true;
+ li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
+ } else {
+ // Do not display pseudolocales unless in development mode.
+ continue;
+ }
+ }
+
if (simCountries.contains(li.getLocale().getCountry())) {
li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
}
@@ -271,19 +288,6 @@
}
}
- boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
- for (String localeId : LocalePicker.getPseudoLocales()) {
- LocaleInfo li = getLocaleInfo(Locale.forLanguageTag(localeId));
- if (isInDeveloperMode) {
- li.setTranslated(true);
- li.mIsPseudo = true;
- li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
- } else {
- sLocaleCache.remove(li.getId());
- }
- }
-
// TODO: See if we can reuse what LocaleList.matchScore does
final HashSet<String> localizedLocales = new HashSet<>();
for (String localeId : LocalePicker.getSystemAssetLocales()) {
diff --git a/core/java/com/android/internal/app/NightDisplayController.java b/core/java/com/android/internal/app/NightDisplayController.java
index 7a1383c..b8bfc64 100644
--- a/core/java/com/android/internal/app/NightDisplayController.java
+++ b/core/java/com/android/internal/app/NightDisplayController.java
@@ -25,7 +25,9 @@
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
+import android.os.SystemProperties;
import android.provider.Settings.Secure;
+import android.provider.Settings.System;
import android.util.Slog;
import com.android.internal.R;
@@ -76,6 +78,35 @@
*/
public static final int AUTO_MODE_TWILIGHT = 2;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED })
+ public @interface ColorMode {}
+
+ /**
+ * Color mode with natural colors.
+ *
+ * @see #setColorMode(int)
+ */
+ public static final int COLOR_MODE_NATURAL = 0;
+ /**
+ * Color mode with boosted colors.
+ *
+ * @see #setColorMode(int)
+ */
+ public static final int COLOR_MODE_BOOSTED = 1;
+ /**
+ * Color mode with saturated colors.
+ *
+ * @see #setColorMode(int)
+ */
+ public static final int COLOR_MODE_SATURATED = 2;
+
+ /**
+ * See com.android.server.display.DisplayTransformManager.
+ */
+ private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation";
+ private static final String PERSISTENT_PROPERTY_NATIVE_MODE = "persist.sys.sf.native_mode";
+
private final Context mContext;
private final int mUserId;
@@ -306,6 +337,37 @@
}
/**
+ * Get the current color mode.
+ */
+ public int getColorMode() {
+ final int colorMode = System.getIntForUser(mContext.getContentResolver(),
+ System.DISPLAY_COLOR_MODE, -1, mUserId);
+ if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) {
+ // There still might be a legacy system property controlling color mode that we need to
+ // respect.
+ if ("1".equals(SystemProperties.get(PERSISTENT_PROPERTY_NATIVE_MODE))) {
+ return COLOR_MODE_SATURATED;
+ }
+ return "1.0".equals(SystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
+ ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
+ }
+ return colorMode;
+ }
+
+ /**
+ * Set the current color mode.
+ *
+ * @param colorMode the color mode
+ */
+ public void setColorMode(@ColorMode int colorMode) {
+ if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) {
+ throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
+ }
+ System.putIntForUser(mContext.getContentResolver(), System.DISPLAY_COLOR_MODE, colorMode,
+ mUserId);
+ }
+
+ /**
* Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated.
*/
public int getMinimumColorTemperature() {
@@ -351,6 +413,9 @@
case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
mCallback.onColorTemperatureChanged(getColorTemperature());
break;
+ case System.DISPLAY_COLOR_MODE:
+ mCallback.onDisplayColorModeChanged(getColorMode());
+ break;
}
}
}
@@ -379,6 +444,8 @@
false /* notifyForDescendants */, mContentObserver, mUserId);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
false /* notifyForDescendants */, mContentObserver, mUserId);
+ cr.registerContentObserver(System.getUriFor(System.DISPLAY_COLOR_MODE),
+ false /* notifyForDecendants */, mContentObserver, mUserId);
}
}
}
@@ -425,5 +492,12 @@
* @param colorTemperature the color temperature to tint the screen
*/
default void onColorTemperatureChanged(int colorTemperature) {}
+
+ /**
+ * Callback invoked when the color mode changes.
+ *
+ * @param displayColorMode the color mode
+ */
+ default void onDisplayColorModeChanged(int displayColorMode) {}
}
}
diff --git a/core/java/com/android/internal/net/OWNERS b/core/java/com/android/internal/net/OWNERS
index 7cb32ff..e2064a8 100644
--- a/core/java/com/android/internal/net/OWNERS
+++ b/core/java/com/android/internal/net/OWNERS
@@ -2,5 +2,5 @@
ek@google.com
hugobenichi@google.com
-jsharkey@google.com
+jsharkey@android.com
lorenzo@google.com
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index 3baccee..05ec9e9 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -106,6 +106,14 @@
}
/**
+ * Peek the next argument on the command line, whatever it is; if there are
+ * no arguments left, return null.
+ */
+ public String peekNextArg() {
+ return mArgs.peekNextArg();
+ }
+
+ /**
* Return the next argument on the command line, whatever it is; if there are
* no arguments left, throws an IllegalArgumentException to report this to the user.
*/
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 239d709..0535ebe 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -63,6 +63,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
+import android.util.StatsLog;
import android.util.TimeUtils;
import android.util.Xml;
import android.view.Display;
@@ -4030,6 +4031,7 @@
}
addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
historyName, uid);
+ StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 1);
}
public void noteLongPartialWakelockFinish(String name, String historyName, int uid) {
@@ -4045,6 +4047,7 @@
}
addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
historyName, uid);
+ StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 0);
}
void aggregateLastWakeupUptimeLocked(long uptimeMs) {
@@ -4052,6 +4055,7 @@
long deltaUptime = uptimeMs - mLastWakeupUptimeMs;
SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason);
timer.add(deltaUptime * 1000, 1); // time in in microseconds
+ StatsLog.write(StatsLog.KERNEL_WAKEUP_REPORTED, mLastWakeupReason, deltaUptime * 1000);
mLastWakeupReason = null;
}
}
@@ -4403,6 +4407,7 @@
mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtime);
}
addHistoryRecordLocked(elapsedRealtime, uptime);
+ StatsLog.write(StatsLog.BATTERY_SAVER_MODE_STATE_CHANGED, enabled ? 1 : 0);
}
}
@@ -4629,6 +4634,7 @@
if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
+ Integer.toHexString(mHistoryCur.states));
newHistory = true;
+ StatsLog.write(StatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin);
} else {
stopAllPhoneSignalStrengthTimersLocked(-1);
}
@@ -5184,6 +5190,7 @@
if (strengthBin >= 0) {
if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) {
mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime);
+ StatsLog.write(StatsLog.WIFI_SIGNAL_STRENGTH_CHANGED, strengthBin);
}
mHistoryCur.states2 =
(mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
@@ -5405,6 +5412,18 @@
}
}
+ public String[] getWifiIfaces() {
+ synchronized (mWifiNetworkLock) {
+ return mWifiIfaces;
+ }
+ }
+
+ public String[] getMobileIfaces() {
+ synchronized (mModemNetworkLock) {
+ return mModemIfaces;
+ }
+ }
+
@Override public long getScreenOnTime(long elapsedRealtimeUs, int which) {
return mScreenOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@@ -6049,6 +6068,8 @@
mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase);
}
mFullWifiLockTimer.startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 1);
}
}
@@ -6057,6 +6078,10 @@
if (mFullWifiLockOut) {
mFullWifiLockOut = false;
mFullWifiLockTimer.stopRunningLocked(elapsedRealtimeMs);
+ if (!mFullWifiLockTimer.isRunningLocked()) { // only tell statsd if truly stopped
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 0);
+ }
}
}
@@ -6070,6 +6095,8 @@
mOnBatteryBackgroundTimeBase);
}
mWifiScanTimer.startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 1);
}
}
@@ -6078,6 +6105,10 @@
if (mWifiScanStarted) {
mWifiScanStarted = false;
mWifiScanTimer.stopRunningLocked(elapsedRealtimeMs);
+ if (!mWifiScanTimer.isRunningLocked()) { // only tell statsd if truly stopped
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 0);
+ }
}
}
@@ -6180,17 +6211,25 @@
public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) {
createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 1);
}
public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) {
if (mAudioTurnedOnTimer != null) {
mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+ if (!mAudioTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0);
+ }
}
}
public void noteResetAudioLocked(long elapsedRealtimeMs) {
if (mAudioTurnedOnTimer != null) {
mAudioTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0);
}
}
@@ -6204,17 +6243,25 @@
public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) {
createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 1);
}
public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) {
if (mVideoTurnedOnTimer != null) {
mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+ if (!mVideoTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0);
+ }
}
}
public void noteResetVideoLocked(long elapsedRealtimeMs) {
if (mVideoTurnedOnTimer != null) {
mVideoTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0);
}
}
@@ -6228,17 +6275,25 @@
public void noteFlashlightTurnedOnLocked(long elapsedRealtimeMs) {
createFlashlightTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 1);
}
public void noteFlashlightTurnedOffLocked(long elapsedRealtimeMs) {
if (mFlashlightTurnedOnTimer != null) {
mFlashlightTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+ if (!mFlashlightTurnedOnTimer.isRunningLocked()) {
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0);
+ }
}
}
public void noteResetFlashlightLocked(long elapsedRealtimeMs) {
if (mFlashlightTurnedOnTimer != null) {
mFlashlightTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0);
}
}
@@ -6252,17 +6307,25 @@
public void noteCameraTurnedOnLocked(long elapsedRealtimeMs) {
createCameraTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 1);
}
public void noteCameraTurnedOffLocked(long elapsedRealtimeMs) {
if (mCameraTurnedOnTimer != null) {
mCameraTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+ if (!mCameraTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0);
+ }
}
}
public void noteResetCameraLocked(long elapsedRealtimeMs) {
if (mCameraTurnedOnTimer != null) {
mCameraTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0);
}
}
@@ -6311,26 +6374,42 @@
public void noteBluetoothScanStartedLocked(long elapsedRealtimeMs, boolean isUnoptimized) {
createBluetoothScanTimerLocked().startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 1);
if (isUnoptimized) {
createBluetoothUnoptimizedScanTimerLocked().startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 1);
}
}
public void noteBluetoothScanStoppedLocked(long elapsedRealtimeMs, boolean isUnoptimized) {
if (mBluetoothScanTimer != null) {
mBluetoothScanTimer.stopRunningLocked(elapsedRealtimeMs);
+ if (!mBluetoothScanTimer.isRunningLocked()) { // only tell statsd if truly stopped
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 0);
+ }
}
if (isUnoptimized && mBluetoothUnoptimizedScanTimer != null) {
mBluetoothUnoptimizedScanTimer.stopRunningLocked(elapsedRealtimeMs);
+ if (!mBluetoothUnoptimizedScanTimer.isRunningLocked()) {
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 0);
+ }
}
}
public void noteResetBluetoothScanLocked(long elapsedRealtimeMs) {
if (mBluetoothScanTimer != null) {
mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 0);
}
if (mBluetoothUnoptimizedScanTimer != null) {
mBluetoothUnoptimizedScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 0);
}
}
@@ -6352,6 +6431,9 @@
createBluetoothScanResultCounterLocked().addAtomic(numNewResults);
// Uses background timebase, so the count will only be incremented if uid in background.
createBluetoothScanResultBgCounterLocked().addAtomic(numNewResults);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ // TODO(statsd): This could be in AppScanStats instead, if desired.
+ StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, getUid(), numNewResults);
}
@Override
@@ -8725,6 +8807,8 @@
DualTimer t = mSyncStats.startObject(name);
if (t != null) {
t.startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 1);
}
}
@@ -8732,6 +8816,10 @@
DualTimer t = mSyncStats.stopObject(name);
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
+ if (!t.isRunningLocked()) { // only tell statsd if truly stopped
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 0);
+ }
}
}
@@ -8739,6 +8827,8 @@
DualTimer t = mJobStats.startObject(name);
if (t != null) {
t.startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 1);
}
}
@@ -8746,6 +8836,10 @@
DualTimer t = mJobStats.stopObject(name);
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
+ if (!t.isRunningLocked()) { // only tell statsd if truly stopped
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 0);
+ }
}
if (mBsi.mOnBatteryTimeBase.isRunning()) {
SparseIntArray types = mJobCompletions.get(name);
@@ -8809,9 +8903,13 @@
Wakelock wl = mWakelockStats.startObject(name);
if (wl != null) {
getWakelockTimerLocked(wl, type).startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Hopefully use a worksource instead of a uid (so move elsewhere)
+ StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 1);
}
if (type == WAKE_TYPE_PARTIAL) {
createAggregatedPartialWakelockTimerLocked().startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.UID_WAKELOCK_STATE_CHANGED, getUid(), type, 1);
if (pid >= 0) {
Pid p = getPidStatsLocked(pid);
if (p.mWakeNesting++ == 0) {
@@ -8824,11 +8922,21 @@
public void noteStopWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
Wakelock wl = mWakelockStats.stopObject(name);
if (wl != null) {
- getWakelockTimerLocked(wl, type).stopRunningLocked(elapsedRealtimeMs);
+ StopwatchTimer wlt = getWakelockTimerLocked(wl, type);
+ wlt.stopRunningLocked(elapsedRealtimeMs);
+ if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 0);
+ }
}
if (type == WAKE_TYPE_PARTIAL) {
if (mAggregatedPartialWakelockTimer != null) {
mAggregatedPartialWakelockTimer.stopRunningLocked(elapsedRealtimeMs);
+ if (!mAggregatedPartialWakelockTimer.isRunningLocked()) {
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ StatsLog.write(StatsLog.UID_WAKELOCK_STATE_CHANGED, getUid(), type,
+ 0);
+ }
}
if (pid >= 0) {
Pid p = mPids.get(pid);
@@ -8852,6 +8960,12 @@
public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
t.startRunningLocked(elapsedRealtimeMs);
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ if (sensor == Sensor.GPS) {
+ StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 1);
+ } else {
+ StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 1);
+ }
}
public void noteStopSensor(int sensor, long elapsedRealtimeMs) {
@@ -8859,6 +8973,14 @@
DualTimer t = getSensorTimerLocked(sensor, false);
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
+ if (!t.isRunningLocked()) { // only tell statsd if truly stopped
+ // TODO(statsd): Possibly use a worksource instead of a uid.
+ if (sensor == Sensor.GPS) {
+ StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 0);
+ } else {
+ StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 0);
+ }
+ }
}
}
@@ -11068,11 +11190,15 @@
// This should probably be exposed in the API, though it's not critical
public static final int BATTERY_PLUGGED_NONE = 0;
- public void setBatteryStateLocked(int status, int health, int plugType, int level,
- int temp, int volt, int chargeUAh, int chargeFullUAh) {
+ public void setBatteryStateLocked(final int status, final int health, final int plugType,
+ final int level, /* not final */ int temp, final int volt, final int chargeUAh,
+ final int chargeFullUAh) {
// Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
temp = Math.max(0, temp);
+ reportChangesToStatsLog(mHaveBatteryLevel ? mHistoryCur : null,
+ status, plugType, level, temp);
+
final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
final long uptime = mClocks.uptimeMillis();
final long elapsedRealtime = mClocks.elapsedRealtime();
@@ -11249,6 +11375,24 @@
mMaxLearnedBatteryCapacity = Math.max(mMaxLearnedBatteryCapacity, chargeFullUAh);
}
+ // Inform StatsLog of setBatteryState changes.
+ // If this is the first reporting, pass in recentPast == null.
+ private void reportChangesToStatsLog(HistoryItem recentPast,
+ final int status, final int plugType, final int level, final int temp) {
+
+ if (recentPast == null || recentPast.batteryStatus != status) {
+ StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, status);
+ }
+ if (recentPast == null || recentPast.batteryPlugType != plugType) {
+ StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, plugType);
+ }
+ if (recentPast == null || recentPast.batteryLevel != level) {
+ StatsLog.write(StatsLog.BATTERY_LEVEL_CHANGED, level);
+ }
+ // Let's just always print the temperature, regardless of whether it changed.
+ StatsLog.write(StatsLog.DEVICE_TEMPERATURE_REPORTED, temp);
+ }
+
public long getAwakeTimeBattery() {
return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT);
}
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index ea4575a..5bddd2f 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -16,9 +16,15 @@
package com.android.internal.os;
+import android.annotation.NonNull;
+import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.Preconditions;
import dalvik.system.VMRuntime;
@@ -31,11 +37,14 @@
* @see IBinder
*/
public class BinderInternal {
+ private static final String TAG = "BinderInternal";
static WeakReference<GcWatcher> sGcWatcher
= new WeakReference<GcWatcher>(new GcWatcher());
static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
static Runnable[] sTmpWatchers = new Runnable[1];
static long sLastGcTime;
+ static final BinderProxyLimitListenerDelegate sBinderProxyLimitListenerDelegate =
+ new BinderProxyLimitListenerDelegate();
static final class GcWatcher {
@Override
@@ -106,4 +115,96 @@
static void forceBinderGc() {
forceGc("Binder");
}
+
+ /**
+ * Enable/disable Binder Proxy Instance Counting by Uid. While enabled, the set callback will
+ * be called if this process holds too many Binder Proxies on behalf of a Uid.
+ * @param enabled true to enable counting, false to disable
+ */
+ public static final native void nSetBinderProxyCountEnabled(boolean enabled);
+
+ /**
+ * Get the current number of Binder Proxies held for each uid.
+ * @return SparseIntArray mapping uids to the number of Binder Proxies currently held
+ */
+ public static final native SparseIntArray nGetBinderProxyPerUidCounts();
+
+ /**
+ * Get the current number of Binder Proxies held for an individual uid.
+ * @param uid Requested uid for Binder Proxy count
+ * @return int with the number of Binder proxies held for a uid
+ */
+ public static final native int nGetBinderProxyCount(int uid);
+
+ /**
+ * Set the Binder Proxy watermarks. Default high watermark = 2500. Default low watermark = 2000
+ * @param high The limit at which the BinderProxyListener callback will be called.
+ * @param low The threshold a binder count must drop below before the callback
+ * can be called again. (This is to avoid many repeated calls to the
+ * callback in a brief period of time)
+ */
+ public static final native void nSetBinderProxyCountWatermarks(int high, int low);
+
+ /**
+ * Interface for callback invocation when the Binder Proxy limit is reached. onLimitReached will
+ * be called with the uid of the app causing too many Binder Proxies
+ */
+ public interface BinderProxyLimitListener {
+ public void onLimitReached(int uid);
+ }
+
+ /**
+ * Callback used by native code to trigger a callback in java code. The callback will be
+ * triggered when too many binder proxies from a uid hits the allowed limit.
+ * @param uid The uid of the bad behaving app sending too many binders
+ */
+ public static void binderProxyLimitCallbackFromNative(int uid) {
+ sBinderProxyLimitListenerDelegate.notifyClient(uid);
+ }
+
+ /**
+ * Set a callback to be triggered when a uid's Binder Proxy limit is reached for this process.
+ * @param listener OnLimitReached of listener will be called in the thread provided by handler
+ * @param handler must not be null, callback will be posted through the handler;
+ *
+ */
+ public static void setBinderProxyCountCallback(BinderProxyLimitListener listener,
+ @NonNull Handler handler) {
+ Preconditions.checkNotNull(handler,
+ "Must provide NonNull Handler to setBinderProxyCountCallback when setting "
+ + "BinderProxyLimitListener");
+ sBinderProxyLimitListenerDelegate.setListener(listener, handler);
+ }
+
+ /**
+ * Clear the Binder Proxy callback
+ */
+ public static void clearBinderProxyCountCallback() {
+ sBinderProxyLimitListenerDelegate.setListener(null, null);
+ }
+
+ static private class BinderProxyLimitListenerDelegate {
+ private BinderProxyLimitListener mBinderProxyLimitListener;
+ private Handler mHandler;
+
+ void setListener(BinderProxyLimitListener listener, Handler handler) {
+ synchronized (this) {
+ mBinderProxyLimitListener = listener;
+ mHandler = handler;
+ }
+ }
+
+ void notifyClient(final int uid) {
+ synchronized (this) {
+ if (mBinderProxyLimitListener != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mBinderProxyLimitListener.onLimitReached(uid);
+ }
+ });
+ }
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/IShellCallback.aidl b/core/java/com/android/internal/os/IShellCallback.aidl
index 57d6789..5704342 100644
--- a/core/java/com/android/internal/os/IShellCallback.aidl
+++ b/core/java/com/android/internal/os/IShellCallback.aidl
@@ -20,5 +20,5 @@
/** @hide */
interface IShellCallback {
- ParcelFileDescriptor openOutputFile(String path, String seLinuxContext);
+ ParcelFileDescriptor openFile(String path, String seLinuxContext, String mode);
}
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index fb6b8b0..3af3e2a 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -16,6 +16,10 @@
package com.android.internal.policy;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -99,11 +103,12 @@
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
boolean isHorizontalDivision, Rect insets) {
- this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets, false);
+ this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
+ DOCKED_INVALID, false);
}
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
- boolean isHorizontalDivision, Rect insets, boolean isMinimizedMode) {
+ boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode) {
mMinFlingVelocityPxPerSecond =
MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
mMinDismissVelocityPxPerSecond =
@@ -121,7 +126,7 @@
com.android.internal.R.dimen.default_minimal_size_resizable_task);
mTaskHeightInMinimizedMode = res.getDimensionPixelSize(
com.android.internal.R.dimen.task_height_of_minimized_mode);
- calculateTargets(isHorizontalDivision);
+ calculateTargets(isHorizontalDivision, dockSide);
mFirstSplitTarget = mTargets.get(1);
mLastSplitTarget = mTargets.get(mTargets.size() - 2);
mDismissStartTarget = mTargets.get(0);
@@ -254,7 +259,7 @@
return mTargets.get(minIndex);
}
- private void calculateTargets(boolean isHorizontalDivision) {
+ private void calculateTargets(boolean isHorizontalDivision, int dockedSide) {
mTargets.clear();
int dividerMax = isHorizontalDivision
? mDisplayHeight
@@ -273,7 +278,7 @@
addMiddleTarget(isHorizontalDivision);
break;
case SNAP_MODE_MINIMIZED:
- addMinimizedTarget(isHorizontalDivision);
+ addMinimizedTarget(isHorizontalDivision, dockedSide);
break;
}
mTargets.add(new SnapTarget(dividerMax - navBarSize, dividerMax,
@@ -331,12 +336,16 @@
mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
}
- private void addMinimizedTarget(boolean isHorizontalDivision) {
+ private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
// In portrait offset the position by the statusbar height, in landscape add the statusbar
// height as well to match portrait offset
int position = mTaskHeightInMinimizedMode + mInsets.top;
if (!isHorizontalDivision) {
- position += mInsets.left;
+ if (dockedSide == DOCKED_LEFT) {
+ position += mInsets.left;
+ } else if (dockedSide == DOCKED_RIGHT) {
+ position = mDisplayWidth - position - mInsets.right;
+ }
}
mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
}
diff --git a/core/java/com/android/internal/util/LocalLog.java b/core/java/com/android/internal/util/LocalLog.java
index f0e6171..8edb739 100644
--- a/core/java/com/android/internal/util/LocalLog.java
+++ b/core/java/com/android/internal/util/LocalLog.java
@@ -20,6 +20,7 @@
import java.util.ArrayList;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
/**
* Helper class for logging serious issues, which also keeps a small
@@ -63,4 +64,16 @@
return true;
}
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ synchronized (mLines) {
+ for (int i = 0; i < mLines.size(); ++i) {
+ proto.write(LocalLogProto.LINES, mLines.get(i));
+ }
+ }
+
+ proto.end(token);
+ }
}
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 8d9630f..e5ad1f4 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -804,7 +804,7 @@
/** State that processed the message */
State msgProcessedState = null;
- if (mIsConstructionCompleted) {
+ if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
/** Normal path */
msgProcessedState = processMsg(msg);
} else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 2098ebd..5b65bbe 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -36,6 +36,7 @@
import android.view.inputmethod.InputContentInfo;
import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicBoolean;
public class InputConnectionWrapper implements InputConnection {
private static final int MAX_WAIT_TIME_MILLIS = 2000;
@@ -46,6 +47,14 @@
@MissingMethodFlags
private final int mMissingMethods;
+ /**
+ * {@code true} if the system already decided to take away IME focus from the target app. This
+ * can be signaled even when the corresponding signal is in the task queue and
+ * {@link InputMethodService#onUnbindInput()} is not yet called back on the UI thread.
+ */
+ @NonNull
+ private final AtomicBoolean mIsUnbindIssued;
+
static class InputContextCallback extends IInputContextCallback.Stub {
private static final String TAG = "InputConnectionWrapper.ICC";
public int mSeq;
@@ -231,14 +240,20 @@
public InputConnectionWrapper(
@NonNull WeakReference<AbstractInputMethodService> inputMethodService,
- IInputContext inputContext, @MissingMethodFlags final int missingMethods) {
+ IInputContext inputContext, @MissingMethodFlags final int missingMethods,
+ @NonNull AtomicBoolean isUnbindIssued) {
mInputMethodService = inputMethodService;
mIInputContext = inputContext;
mMissingMethods = missingMethods;
+ mIsUnbindIssued = isUnbindIssued;
}
@AnyThread
public CharSequence getTextAfterCursor(int length, int flags) {
+ if (mIsUnbindIssued.get()) {
+ return null;
+ }
+
CharSequence value = null;
try {
InputContextCallback callback = InputContextCallback.getInstance();
@@ -258,6 +273,10 @@
@AnyThread
public CharSequence getTextBeforeCursor(int length, int flags) {
+ if (mIsUnbindIssued.get()) {
+ return null;
+ }
+
CharSequence value = null;
try {
InputContextCallback callback = InputContextCallback.getInstance();
@@ -277,6 +296,10 @@
@AnyThread
public CharSequence getSelectedText(int flags) {
+ if (mIsUnbindIssued.get()) {
+ return null;
+ }
+
if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) {
// This method is not implemented.
return null;
@@ -300,6 +323,10 @@
@AnyThread
public int getCursorCapsMode(int reqModes) {
+ if (mIsUnbindIssued.get()) {
+ return 0;
+ }
+
int value = 0;
try {
InputContextCallback callback = InputContextCallback.getInstance();
@@ -319,6 +346,10 @@
@AnyThread
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ if (mIsUnbindIssued.get()) {
+ return null;
+ }
+
ExtractedText value = null;
try {
InputContextCallback callback = InputContextCallback.getInstance();
@@ -516,6 +547,10 @@
@AnyThread
public boolean requestCursorUpdates(int cursorUpdateMode) {
+ if (mIsUnbindIssued.get()) {
+ return false;
+ }
+
boolean result = false;
if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
// This method is not implemented.
@@ -550,6 +585,10 @@
@AnyThread
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+ if (mIsUnbindIssued.get()) {
+ return false;
+ }
+
boolean result = false;
if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) {
// This method is not implemented.
diff --git a/core/java/com/android/internal/widget/Magnifier.java b/core/java/com/android/internal/widget/Magnifier.java
index 6d54d7b..f6741c3 100644
--- a/core/java/com/android/internal/widget/Magnifier.java
+++ b/core/java/com/android/internal/widget/Magnifier.java
@@ -63,7 +63,7 @@
// the copy is finished.
private final Handler mPixelCopyHandler = Handler.getMain();
// Current magnification scale.
- private float mScale;
+ private final float mZoomScale;
// Timer used to schedule the copy task.
private Timer mTimer;
@@ -76,11 +76,12 @@
public Magnifier(@NonNull View view) {
mView = Preconditions.checkNotNull(view);
final Context context = mView.getContext();
+ final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
content.findViewById(R.id.magnifier_inner).setClipToOutline(true);
mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
- final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
+ mZoomScale = context.getResources().getFloat(R.dimen.magnifier_zoom_scale);
mWindow = new PopupWindow(context);
mWindow.setContentView(content);
@@ -90,7 +91,9 @@
mWindow.setTouchable(false);
mWindow.setBackgroundDrawable(null);
- mBitmap = Bitmap.createBitmap(mWindowWidth, mWindowHeight, Bitmap.Config.ARGB_8888);
+ final int bitmapWidth = (int) (mWindowWidth / mZoomScale);
+ final int bitmapHeight = (int) (mWindowHeight / mZoomScale);
+ mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
getImageView().setImageBitmap(mBitmap);
}
@@ -101,20 +104,8 @@
* to the view. The lower end is clamped to 0
* @param yPosInView vertical coordinate of the center point of the magnifier source
* relative to the view. The lower end is clamped to 0
- * @param scale the scale at which the magnifier zooms on the source content. The
- * lower end is clamped to 1 and the higher end to 4
*/
- public void show(@FloatRange(from=0) float xPosInView,
- @FloatRange(from=0) float yPosInView,
- @FloatRange(from=1, to=4) float scale) {
- if (scale > 4) {
- scale = 4;
- }
-
- if (scale < 1) {
- scale = 1;
- }
-
+ public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) {
if (xPosInView < 0) {
xPosInView = 0;
}
@@ -123,10 +114,6 @@
yPosInView = 0;
}
- if (mScale != scale) {
- resizeBitmap(scale);
- }
- mScale = scale;
configureCoordinates(xPosInView, yPosInView);
if (mTimer == null) {
@@ -175,11 +162,11 @@
return mWindowWidth;
}
- private void resizeBitmap(float scale) {
- final int bitmapWidth = (int) (mWindowWidth / scale);
- final int bitmapHeight = (int) (mWindowHeight / scale);
- mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
- getImageView().setImageBitmap(mBitmap);
+ /**
+ * @return the zoom scale of the magnifier.
+ */
+ public float getZoomScale() {
+ return mZoomScale;
}
private void configureCoordinates(float xPosInView, float yPosInView) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 4fbd265..fb8b9f7 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -38,6 +38,7 @@
],
cppflags: ["-Wno-conversion-null"],
+ cpp_std: "c++17",
srcs: [
"AndroidRuntime.cpp",
@@ -184,7 +185,6 @@
"android/opengl/poly_clip.cpp", // TODO: .arm
"android/opengl/util.cpp",
"android_server_NetworkManagementSocketTagger.cpp",
- "android_server_Watchdog.cpp",
"android_ddm_DdmHandleNativeHeap.cpp",
"android_backup_BackupDataInput.cpp",
"android_backup_BackupDataOutput.cpp",
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index c6801bf..9e6985c 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -56,9 +56,9 @@
if (langs != nullptr) {
ScopedUtfChars str(env, langs);
builder = new NativeFamilyBuilder(
- minikin::FontStyle::registerLanguageList(str.c_str()), variant);
+ minikin::FontStyle::registerLocaleList(str.c_str()), variant);
} else {
- builder = new NativeFamilyBuilder(minikin::FontStyle::registerLanguageList(""), variant);
+ builder = new NativeFamilyBuilder(minikin::FontStyle::registerLocaleList(""), variant);
}
return reinterpret_cast<jlong>(builder);
}
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 93bd16b2..e8e3f57 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -112,8 +112,8 @@
float measured = 0;
std::unique_ptr<float[]> advancesArray(new float[count]);
- MinikinUtils::measureText(&paint, bidiFlags, typeface, text, 0, count, count,
- advancesArray.get());
+ MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text,
+ 0, count, count, advancesArray.get());
for (int i = 0; i < count; i++) {
// traverse in the given direction
@@ -203,8 +203,9 @@
if (advances) {
advancesArray.reset(new jfloat[count]);
}
- const float advance = MinikinUtils::measureText(paint, bidiFlags, typeface, text,
- start, count, contextCount, advancesArray.get());
+ const float advance = MinikinUtils::measureText(paint,
+ static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count, contextCount,
+ advancesArray.get());
if (advances) {
env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
}
@@ -239,7 +240,7 @@
static jint doTextRunCursor(JNIEnv *env, Paint* paint, const Typeface* typeface,
const jchar *text, jint start, jint count, jint dir, jint offset, jint opt) {
minikin::GraphemeBreak::MoveOpt moveOpt = minikin::GraphemeBreak::MoveOpt(opt);
- int bidiFlags = dir == 1 ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR;
+ minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count,
advancesArray.get());
@@ -305,7 +306,7 @@
static void getTextPath(JNIEnv* env, Paint* paint, const Typeface* typeface, const jchar* text,
jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) {
minikin::Layout layout = MinikinUtils::doLayout(
- paint, bidiFlags, typeface, text, 0, count, count);
+ paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count);
size_t nGlyphs = layout.nGlyphs();
uint16_t* glyphs = new uint16_t[nGlyphs];
SkPoint* pos = new SkPoint[nGlyphs];
@@ -346,8 +347,8 @@
SkRect r;
SkIRect ir;
- minikin::Layout layout = MinikinUtils::doLayout(
- &paint, bidiFlags, typeface, text, 0, count, count);
+ minikin::Layout layout = MinikinUtils::doLayout(&paint,
+ static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count);
minikin::MinikinRect rect;
layout.getBounds(&rect);
r.fLeft = rect.mLeft;
@@ -461,8 +462,9 @@
nChars++;
prevCp = cp;
}
- minikin::Layout layout = MinikinUtils::doLayout(
- paint, bidiFlags, typeface, str.get(), 0, str.size(), str.size());
+ minikin::Layout layout = MinikinUtils::doLayout(paint,
+ static_cast<minikin::Bidi>(bidiFlags), typeface, str.get(), 0, str.size(),
+ str.size());
size_t nGlyphs = countNonSpaceGlyphs(layout);
if (nGlyphs != 1 && nChars > 1) {
// multiple-character input, and was not a ligature
@@ -481,8 +483,8 @@
// since ZZ is reserved for unknown or invalid territory.
// U+1F1FF (REGIONAL INDICATOR SYMBOL LETTER Z) is \uD83C\uDDFF in UTF16.
static const jchar ZZ_FLAG_STR[] = { 0xD83C, 0xDDFF, 0xD83C, 0xDDFF };
- minikin::Layout zzLayout = MinikinUtils::doLayout(
- paint, bidiFlags, typeface, ZZ_FLAG_STR, 0, 4, 4);
+ minikin::Layout zzLayout = MinikinUtils::doLayout(paint,
+ static_cast<minikin::Bidi>(bidiFlags), typeface, ZZ_FLAG_STR, 0, 4, 4);
if (zzLayout.nGlyphs() != 1 || layoutContainsNotdef(zzLayout)) {
// The font collection doesn't have a glyph for unknown flag. Just return true.
return true;
@@ -494,7 +496,7 @@
static jfloat doRunAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[],
jint start, jint count, jint bufSize, jboolean isRtl, jint offset) {
- int bidiFlags = isRtl ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR;
+ minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
if (offset == start + count) {
return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
bufSize, nullptr);
@@ -519,7 +521,7 @@
static jint doOffsetForAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[],
jint start, jint count, jint bufSize, jboolean isRtl, jfloat advance) {
- int bidiFlags = isRtl ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR;
+ minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
advancesArray.get());
@@ -544,7 +546,7 @@
static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
Paint* obj = reinterpret_cast<Paint*>(objHandle);
ScopedUtfChars localesChars(env, locales);
- jint minikinLangListId = minikin::FontStyle::registerLanguageList(localesChars.c_str());
+ jint minikinLangListId = minikin::FontStyle::registerLocaleList(localesChars.c_str());
obj->setMinikinLangListId(minikinLangListId);
return minikinLangListId;
}
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index c3f9bf7..1efff7f 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -765,6 +765,14 @@
if (startPos > totalRows) {
ALOGE("startPos %d > actual rows %d", startPos, totalRows);
}
+ if (totalRows > 0 && addedRows == 0) {
+ String8 msg;
+ msg.appendFormat("Row too big to fit into CursorWindow requiredPos=%d, totalRows=%d",
+ requiredPos, totalRows);
+ throw_sqlite3_exception(env, SQLITE_TOOBIG, NULL, msg.string());
+ return 0;
+ }
+
jlong result = jlong(startPos) << 32 | jlong(totalRows);
return result;
}
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index fba0721..f08b89c 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -480,7 +480,7 @@
const Typeface* typeface = paint->getAndroidTypeface();
jchar* jchars = env->GetCharArrayElements(text, NULL);
get_canvas(canvasHandle)->drawText(jchars + index, 0, count, count, x, y,
- bidiFlags, *paint, typeface);
+ static_cast<minikin::Bidi>(bidiFlags), *paint, typeface);
env->ReleaseCharArrayElements(text, jchars, JNI_ABORT);
}
@@ -492,7 +492,7 @@
const int count = end - start;
const jchar* jchars = env->GetStringChars(text, NULL);
get_canvas(canvasHandle)->drawText(jchars + start, 0, count, count, x, y,
- bidiFlags, *paint, typeface);
+ static_cast<minikin::Bidi>(bidiFlags), *paint, typeface);
env->ReleaseStringChars(text, jchars);
}
@@ -502,7 +502,7 @@
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
- const int bidiFlags = isRtl ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR;
+ const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
jchar* jchars = env->GetCharArrayElements(text, NULL);
get_canvas(canvasHandle)->drawText(jchars + contextIndex, index - contextIndex, count,
contextCount, x, y, bidiFlags, *paint, typeface);
@@ -515,7 +515,7 @@
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
- int bidiFlags = isRtl ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR;
+ const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
jint count = end - start;
jint contextCount = contextEnd - contextStart;
const jchar* jchars = env->GetStringChars(text, NULL);
@@ -533,8 +533,8 @@
jchar* jchars = env->GetCharArrayElements(text, NULL);
- get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count, bidiFlags, *path,
- hOffset, vOffset, *paint, typeface);
+ get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count,
+ static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface);
env->ReleaseCharArrayElements(text, jchars, 0);
}
@@ -549,8 +549,8 @@
const jchar* jchars = env->GetStringChars(text, NULL);
int count = env->GetStringLength(text);
- get_canvas(canvasHandle)->drawTextOnPath(jchars, count, bidiFlags, *path,
- hOffset, vOffset, *paint, typeface);
+ get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags),
+ *path, hOffset, vOffset, *paint, typeface);
env->ReleaseStringChars(text, jchars);
}
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index fe14d48..f6783e1 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -35,6 +35,7 @@
#include <hidl/HidlTransportSupport.h>
#include <hwbinder/ProcessState.h>
#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
#include <vintf/parse_string.h>
#include <utils/misc.h>
@@ -261,14 +262,9 @@
JNIEnv *env,
jobject thiz,
jstring serviceNameObj) {
- if (serviceNameObj == NULL) {
- jniThrowException(env, "java/lang/NullPointerException", NULL);
- return;
- }
-
- const char *serviceName = env->GetStringUTFChars(serviceNameObj, NULL);
- if (serviceName == NULL) {
- return; // XXX exception already pending?
+ ScopedUtfChars str(env, serviceNameObj);
+ if (str.c_str() == nullptr) {
+ return; // NPE will be pending.
}
sp<hardware::IBinder> binder = JHwBinder::GetNativeBinder(env, thiz);
@@ -284,15 +280,12 @@
return;
}
- Return<bool> ret = manager->add(serviceName, base);
-
- env->ReleaseStringUTFChars(serviceNameObj, serviceName);
- serviceName = NULL;
+ Return<bool> ret = manager->add(str.c_str(), base);
bool ok = ret.isOk() && ret;
if (ok) {
- LOG(INFO) << "Starting thread pool.";
+ LOG(INFO) << "HwBinder: Starting thread pool for " << str.c_str();
::android::hardware::ProcessState::self()->startThreadPool();
}
@@ -303,36 +296,31 @@
JNIEnv *env,
jclass /* clazzObj */,
jstring ifaceNameObj,
- jstring serviceNameObj) {
+ jstring serviceNameObj,
+ jboolean retry) {
using ::android::hidl::base::V1_0::IBase;
using ::android::hardware::details::getRawServiceInternal;
- if (ifaceNameObj == NULL) {
- jniThrowException(env, "java/lang/NullPointerException", NULL);
- return NULL;
- }
- if (serviceNameObj == NULL) {
- jniThrowException(env, "java/lang/NullPointerException", NULL);
- return NULL;
+ std::string ifaceName;
+ {
+ ScopedUtfChars str(env, ifaceNameObj);
+ if (str.c_str() == nullptr) {
+ return nullptr; // NPE will be pending.
+ }
+ ifaceName = str.c_str();
}
- const char *ifaceNameCStr = env->GetStringUTFChars(ifaceNameObj, NULL);
- if (ifaceNameCStr == NULL) {
- return NULL; // XXX exception already pending?
+ std::string serviceName;
+ {
+ ScopedUtfChars str(env, serviceNameObj);
+ if (str.c_str() == nullptr) {
+ return nullptr; // NPE will be pending.
+ }
+ serviceName = str.c_str();
}
- std::string ifaceName(ifaceNameCStr);
- env->ReleaseStringUTFChars(ifaceNameObj, ifaceNameCStr);
- const char *serviceNameCStr = env->GetStringUTFChars(serviceNameObj, NULL);
- if (serviceNameCStr == NULL) {
- return NULL; // XXX exception already pending?
- }
- std::string serviceName(serviceNameCStr);
- env->ReleaseStringUTFChars(serviceNameObj, serviceNameCStr);
-
- // TODO(b/67981006): true /* retry */
- sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, false /* retry */, false /* getStub */);
+ sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, retry /* retry */, false /* getStub */);
sp<hardware::IBinder> service = hardware::toBinder<hidl::base::V1_0::IBase>(ret);
if (service == NULL) {
@@ -340,13 +328,14 @@
return NULL;
}
- LOG(INFO) << "Starting thread pool.";
+ LOG(INFO) << "HwBinder: Starting thread pool for " << serviceName << "::" << ifaceName;
::android::hardware::ProcessState::self()->startThreadPool();
return JHwRemoteBinder::NewObject(env, service);
}
-void JHwBinder_native_configureRpcThreadpool(jlong maxThreads, jboolean callerWillJoin) {
+void JHwBinder_native_configureRpcThreadpool(JNIEnv *, jclass,
+ jlong maxThreads, jboolean callerWillJoin) {
CHECK(maxThreads > 0);
ProcessState::self()->setThreadPoolConfiguration(maxThreads, callerWillJoin /*callerJoinsPool*/);
}
@@ -355,7 +344,18 @@
IPCThreadState::self()->joinThreadPool();
}
-static void JHwBinder_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/)
+void JHwBinder_native_startRpcThreadPool(JNIEnv *, jclass,
+ jlong maxThreads, jboolean callerWillJoin) {
+ CHECK(maxThreads > 0);
+ ProcessState::self()->setThreadPoolConfiguration(maxThreads,
+ callerWillJoin /* callerJoinsPool */);
+ ssize_t threadsNeeded = maxThreads - (callerWillJoin ? 0 : 1);
+ for (ssize_t i = 0; i < threadsNeeded; ++i) {
+ ProcessState::self()->spawnPooledThread(false /* isMain */);
+ }
+}
+
+static void JHwBinder_report_sysprop_change(JNIEnv * /*env*/, jclass /*clazz*/)
{
report_sysprop_change();
}
@@ -371,7 +371,7 @@
{ "registerService", "(Ljava/lang/String;)V",
(void *)JHwBinder_native_registerService },
- { "getService", "(Ljava/lang/String;Ljava/lang/String;)L" PACKAGE_PATH "/IHwBinder;",
+ { "getService", "(Ljava/lang/String;Ljava/lang/String;Z)L" PACKAGE_PATH "/IHwBinder;",
(void *)JHwBinder_native_getService },
{ "configureRpcThreadpool", "(JZ)V",
@@ -380,6 +380,9 @@
{ "joinRpcThreadpool", "()V",
(void *)JHwBinder_native_joinRpcThreadpool },
+ { "startRpcThreadPool", "(JZ)V",
+ (void *)JHwBinder_native_startRpcThreadPool },
+
{ "native_report_sysprop_change", "()V",
(void *)JHwBinder_report_sysprop_change },
};
diff --git a/core/jni/android_os_HwBlob.cpp b/core/jni/android_os_HwBlob.cpp
index 40d49b7..bb916d2 100644
--- a/core/jni/android_os_HwBlob.cpp
+++ b/core/jni/android_os_HwBlob.cpp
@@ -26,6 +26,7 @@
#include <android_runtime/AndroidRuntime.h>
#include <hidl/Status.h>
#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
#include "core_jni_helpers.h"
@@ -60,12 +61,12 @@
JNIEnv *env, jobject thiz, const sp<JHwBlob> &context) {
sp<JHwBlob> old = (JHwBlob *)env->GetLongField(thiz, gFields.contextID);
- if (context != NULL) {
- context->incStrong(NULL /* id */);
+ if (context != nullptr) {
+ context->incStrong(nullptr /* id */);
}
- if (old != NULL) {
- old->decStrong(NULL /* id */);
+ if (old != nullptr) {
+ old->decStrong(nullptr /* id */);
}
env->SetLongField(thiz, gFields.contextID, (long)context.get());
@@ -150,6 +151,10 @@
return mBuffer;
}
+void *JHwBlob::data() {
+ return mBuffer;
+}
+
size_t JHwBlob::size() const {
return mSize;
}
@@ -242,8 +247,8 @@
static void releaseNativeContext(void *nativeContext) {
sp<JHwBlob> parcel = (JHwBlob *)nativeContext;
- if (parcel != NULL) {
- parcel->decStrong(NULL /* id */);
+ if (parcel != nullptr) {
+ parcel->decStrong(nullptr /* id */);
}
}
@@ -313,6 +318,82 @@
return env->NewStringUTF(s->c_str());
}
+#define DEFINE_BLOB_ARRAY_COPIER(Suffix,Type,NewType) \
+static void JHwBlob_native_copyTo ## Suffix ## Array( \
+ JNIEnv *env, \
+ jobject thiz, \
+ jlong offset, \
+ Type ## Array array, \
+ jint size) { \
+ if (array == nullptr) { \
+ jniThrowException(env, "java/lang/NullPointerException", nullptr); \
+ return; \
+ } \
+ \
+ if (env->GetArrayLength(array) < size) { \
+ signalExceptionForError(env, BAD_VALUE); \
+ return; \
+ } \
+ \
+ sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz); \
+ \
+ if ((offset + size * sizeof(Type)) > blob->size()) { \
+ signalExceptionForError(env, -ERANGE); \
+ return; \
+ } \
+ \
+ env->Set ## NewType ## ArrayRegion( \
+ array, \
+ 0 /* start */, \
+ size, \
+ reinterpret_cast<const Type *>( \
+ static_cast<const uint8_t *>(blob->data()) + offset)); \
+}
+
+DEFINE_BLOB_ARRAY_COPIER(Int8,jbyte,Byte)
+DEFINE_BLOB_ARRAY_COPIER(Int16,jshort,Short)
+DEFINE_BLOB_ARRAY_COPIER(Int32,jint,Int)
+DEFINE_BLOB_ARRAY_COPIER(Int64,jlong,Long)
+DEFINE_BLOB_ARRAY_COPIER(Float,jfloat,Float)
+DEFINE_BLOB_ARRAY_COPIER(Double,jdouble,Double)
+
+static void JHwBlob_native_copyToBoolArray(
+ JNIEnv *env,
+ jobject thiz,
+ jlong offset,
+ jbooleanArray array,
+ jint size) {
+ if (array == nullptr) {
+ jniThrowException(env, "java/lang/NullPointerException", nullptr);
+ return;
+ }
+
+ if (env->GetArrayLength(array) < size) {
+ signalExceptionForError(env, BAD_VALUE);
+ return;
+ }
+
+ sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);
+
+ if ((offset + size * sizeof(bool)) > blob->size()) {
+ signalExceptionForError(env, -ERANGE);
+ return;
+ }
+
+ const bool *src =
+ reinterpret_cast<const bool *>(
+ static_cast<const uint8_t *>(blob->data()) + offset);
+
+ jboolean *dst = env->GetBooleanArrayElements(array, nullptr /* isCopy */);
+
+ for (jint i = 0; i < size; ++i) {
+ dst[i] = src[i];
+ }
+
+ env->ReleaseBooleanArrayElements(array, dst, 0 /* mode */);
+ dst = nullptr;
+}
+
#define DEFINE_BLOB_PUTTER(Suffix,Type) \
static void JHwBlob_native_put ## Suffix( \
JNIEnv *env, jobject thiz, jlong offset, Type x) { \
@@ -375,6 +456,59 @@
blob->putBlob(offset + hidl_string::kOffsetOfBuffer, subBlob);
}
+#define DEFINE_BLOB_ARRAY_PUTTER(Suffix,Type,NewType) \
+static void JHwBlob_native_put ## Suffix ## Array( \
+ JNIEnv *env, jobject thiz, jlong offset, Type ## Array array) { \
+ Scoped ## NewType ## ArrayRO autoArray(env, array); \
+ \
+ if (array == nullptr) { \
+ /* NullpointerException already pending */ \
+ return; \
+ } \
+ \
+ sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz); \
+ \
+ status_t err = blob->write( \
+ offset, autoArray.get(), autoArray.size() * sizeof(Type)); \
+ \
+ if (err != OK) { \
+ signalExceptionForError(env, err); \
+ } \
+}
+
+DEFINE_BLOB_ARRAY_PUTTER(Int8,jbyte,Byte)
+DEFINE_BLOB_ARRAY_PUTTER(Int16,jshort,Short)
+DEFINE_BLOB_ARRAY_PUTTER(Int32,jint,Int)
+DEFINE_BLOB_ARRAY_PUTTER(Int64,jlong,Long)
+DEFINE_BLOB_ARRAY_PUTTER(Float,jfloat,Float)
+DEFINE_BLOB_ARRAY_PUTTER(Double,jdouble,Double)
+
+static void JHwBlob_native_putBoolArray(
+ JNIEnv *env, jobject thiz, jlong offset, jbooleanArray array) {
+ ScopedBooleanArrayRO autoArray(env, array);
+
+ if (array == nullptr) {
+ /* NullpointerException already pending */
+ return;
+ }
+
+ sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);
+
+ if ((offset + autoArray.size() * sizeof(bool)) > blob->size()) {
+ signalExceptionForError(env, -ERANGE);
+ return;
+ }
+
+ const jboolean *src = autoArray.get();
+
+ bool *dst = reinterpret_cast<bool *>(
+ static_cast<uint8_t *>(blob->data()) + offset);
+
+ for (size_t i = 0; i < autoArray.size(); ++i) {
+ dst[i] = src[i];
+ }
+}
+
static void JHwBlob_native_putBlob(
JNIEnv *env, jobject thiz, jlong offset, jobject blobObj) {
if (blobObj == nullptr) {
@@ -413,6 +547,14 @@
{ "getDouble", "(J)D", (void *)JHwBlob_native_getDouble },
{ "getString", "(J)Ljava/lang/String;", (void *)JHwBlob_native_getString },
+ { "copyToBoolArray", "(J[ZI)V", (void *)JHwBlob_native_copyToBoolArray },
+ { "copyToInt8Array", "(J[BI)V", (void *)JHwBlob_native_copyToInt8Array },
+ { "copyToInt16Array", "(J[SI)V", (void *)JHwBlob_native_copyToInt16Array },
+ { "copyToInt32Array", "(J[II)V", (void *)JHwBlob_native_copyToInt32Array },
+ { "copyToInt64Array", "(J[JI)V", (void *)JHwBlob_native_copyToInt64Array },
+ { "copyToFloatArray", "(J[FI)V", (void *)JHwBlob_native_copyToFloatArray },
+ { "copyToDoubleArray", "(J[DI)V", (void *)JHwBlob_native_copyToDoubleArray },
+
{ "putBool", "(JZ)V", (void *)JHwBlob_native_putBool },
{ "putInt8", "(JB)V", (void *)JHwBlob_native_putInt8 },
{ "putInt16", "(JS)V", (void *)JHwBlob_native_putInt16 },
@@ -422,6 +564,14 @@
{ "putDouble", "(JD)V", (void *)JHwBlob_native_putDouble },
{ "putString", "(JLjava/lang/String;)V", (void *)JHwBlob_native_putString },
+ { "putBoolArray", "(J[Z)V", (void *)JHwBlob_native_putBoolArray },
+ { "putInt8Array", "(J[B)V", (void *)JHwBlob_native_putInt8Array },
+ { "putInt16Array", "(J[S)V", (void *)JHwBlob_native_putInt16Array },
+ { "putInt32Array", "(J[I)V", (void *)JHwBlob_native_putInt32Array },
+ { "putInt64Array", "(J[J)V", (void *)JHwBlob_native_putInt64Array },
+ { "putFloatArray", "(J[F)V", (void *)JHwBlob_native_putFloatArray },
+ { "putDoubleArray", "(J[D)V", (void *)JHwBlob_native_putDoubleArray },
+
{ "putBlob", "(JL" PACKAGE_PATH "/HwBlob;)V",
(void *)JHwBlob_native_putBlob },
diff --git a/core/jni/android_os_HwBlob.h b/core/jni/android_os_HwBlob.h
index 39393cb..6b1db63 100644
--- a/core/jni/android_os_HwBlob.h
+++ b/core/jni/android_os_HwBlob.h
@@ -50,6 +50,8 @@
size_t offset, const android::hardware::hidl_string **s) const;
const void *data() const;
+ void *data();
+
size_t size() const;
status_t putBlob(size_t offset, const sp<JHwBlob> &blob);
diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp
index cf59a56a..ca5e1e4 100644
--- a/core/jni/android_os_HwRemoteBinder.cpp
+++ b/core/jni/android_os_HwRemoteBinder.cpp
@@ -22,9 +22,13 @@
#include "android_os_HwParcel.h"
-#include <nativehelper/JNIHelp.h>
+#include <android/hidl/base/1.0/IBase.h>
+#include <android/hidl/base/1.0/BpHwBase.h>
+#include <android/hidl/base/1.0/BnHwBase.h>
#include <android_runtime/AndroidRuntime.h>
#include <hidl/Status.h>
+#include <hidl/HidlTransportSupport.h>
+#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
#include <nativehelper/ScopedLocalRef.h>
@@ -413,6 +417,44 @@
return res;
}
+static sp<hidl::base::V1_0::IBase> toIBase(JNIEnv* env, jclass hwRemoteBinderClazz, jobject jbinder)
+{
+ if (jbinder == nullptr) {
+ return nullptr;
+ }
+ if (!env->IsInstanceOf(jbinder, hwRemoteBinderClazz)) {
+ return nullptr;
+ }
+ sp<JHwRemoteBinder> context = JHwRemoteBinder::GetNativeContext(env, jbinder);
+ sp<hardware::IBinder> cbinder = context->getBinder();
+ return hardware::fromBinder<hidl::base::V1_0::IBase, hidl::base::V1_0::BpHwBase,
+ hidl::base::V1_0::BnHwBase>(cbinder);
+}
+
+// equals iff other is also a non-null android.os.HwRemoteBinder object
+// and getBinder() returns the same object.
+// In particular, if other is an android.os.HwBinder object (for stubs) then
+// it returns false.
+static jboolean JHwRemoteBinder_equals(JNIEnv* env, jobject thiz, jobject other)
+{
+ if (env->IsSameObject(thiz, other)) {
+ return true;
+ }
+ if (other == NULL) {
+ return false;
+ }
+
+ ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH));
+
+ return hardware::interfacesEqual(toIBase(env, clazz.get(), thiz), toIBase(env, clazz.get(), other));
+}
+
+static jint JHwRemoteBinder_hashCode(JNIEnv* env, jobject thiz) {
+ jlong longHash = reinterpret_cast<jlong>(
+ JHwRemoteBinder::GetNativeContext(env, thiz)->getBinder().get());
+ return static_cast<jint>(longHash ^ (longHash >> 32)); // See Long.hashCode()
+}
+
static JNINativeMethod gMethods[] = {
{ "native_init", "()J", (void *)JHwRemoteBinder_native_init },
@@ -430,6 +472,11 @@
{"unlinkToDeath",
"(Landroid/os/IHwBinder$DeathRecipient;)Z",
(void*)JHwRemoteBinder_unlinkToDeath},
+
+ {"equals", "(Ljava/lang/Object;)Z",
+ (void*)JHwRemoteBinder_equals},
+
+ {"hashCode", "()I", (void*)JHwRemoteBinder_hashCode},
};
namespace android {
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 5ef2a9e..6243fad 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -102,8 +102,11 @@
cPackageInfo[i] = cString;
env->ReleaseStringUTFChars(element, cString);
}
+ // If we can run this code, the device should already pass AVB.
+ // So, we don't need to check AVB here.
std::string error;
- int32_t status = VintfObject::CheckCompatibility(cPackageInfo, &error);
+ int32_t status = VintfObject::CheckCompatibility(
+ cPackageInfo, &error, ::android::vintf::DISABLE_AVB_CHECK);
if (status)
LOG(WARNING) << "VintfObject.verify() returns " << status << ": " << error;
return status;
diff --git a/core/jni/android_server_Watchdog.cpp b/core/jni/android_server_Watchdog.cpp
deleted file mode 100644
index 01d565b..0000000
--- a/core/jni/android_server_Watchdog.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- ** Copyright 2010, 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.
- */
-
-#define LOG_TAG "Watchdog_N"
-#include <utils/Log.h>
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <string.h>
-#include <errno.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-
-static void dumpOneStack(int tid, int outFd) {
- char buf[64];
-
- snprintf(buf, sizeof(buf), "/proc/%d/stack", tid);
- int stackFd = open(buf, O_RDONLY);
- if (stackFd >= 0) {
- // header for readability
- strncat(buf, ":\n", sizeof(buf) - strlen(buf) - 1);
- write(outFd, buf, strlen(buf));
-
- // copy the stack dump text
- int nBytes;
- while ((nBytes = read(stackFd, buf, sizeof(buf))) > 0) {
- write(outFd, buf, nBytes);
- }
-
- // footer and done
- write(outFd, "\n", 1);
- close(stackFd);
- } else {
- ALOGE("Unable to open stack of tid %d : %d (%s)", tid, errno, strerror(errno));
- }
-}
-
-static void dumpKernelStacks(JNIEnv* env, jobject clazz, jstring pathStr) {
- char buf[128];
- DIR* taskdir;
-
- ALOGI("dumpKernelStacks");
- if (!pathStr) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "Null path");
- return;
- }
-
- const char *path = env->GetStringUTFChars(pathStr, NULL);
-
- int outFd = open(path, O_WRONLY | O_APPEND | O_CREAT,
- S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
- if (outFd < 0) {
- ALOGE("Unable to open stack dump file: %d (%s)", errno, strerror(errno));
- goto done;
- }
-
- snprintf(buf, sizeof(buf), "\n----- begin pid %d kernel stacks -----\n", getpid());
- write(outFd, buf, strlen(buf));
-
- // look up the list of all threads in this process
- snprintf(buf, sizeof(buf), "/proc/%d/task", getpid());
- taskdir = opendir(buf);
- if (taskdir != NULL) {
- struct dirent * ent;
- while ((ent = readdir(taskdir)) != NULL) {
- int tid = atoi(ent->d_name);
- if (tid > 0 && tid <= 65535) {
- // dump each stack trace
- dumpOneStack(tid, outFd);
- }
- }
- closedir(taskdir);
- }
-
- snprintf(buf, sizeof(buf), "----- end pid %d kernel stacks -----\n", getpid());
- write(outFd, buf, strlen(buf));
-
- close(outFd);
-done:
- env->ReleaseStringUTFChars(pathStr, path);
-}
-
-// ----------------------------------------
-
-namespace android {
-
-static const JNINativeMethod g_methods[] = {
- { "native_dumpKernelStacks", "(Ljava/lang/String;)V", (void*)dumpKernelStacks },
-};
-
-int register_android_server_Watchdog(JNIEnv* env) {
- return RegisterMethodsOrDie(env, "com/android/server/Watchdog", g_methods, NELEM(g_methods));
-}
-
-}
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 05bec28..6f9cc22 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -82,45 +82,45 @@
constexpr int INDIC_MIN_PREFIX = 2;
constexpr int INDIC_MIN_SUFFIX = 2;
- addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese
- addHyphenator("be", 2, 2); // Belarusian
- addHyphenator("bg", 2, 2); // Bulgarian
- addHyphenator("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali
- addHyphenator("cu", 1, 2); // Church Slavonic
- addHyphenator("cy", 2, 3); // Welsh
- addHyphenator("da", 2, 2); // Danish
- addHyphenator("de-1901", 2, 2); // German 1901 orthography
- addHyphenator("de-1996", 2, 2); // German 1996 orthography
- addHyphenator("de-CH-1901", 2, 2); // Swiss High German 1901 orthography
- addHyphenator("en-GB", 2, 3); // British English
- addHyphenator("en-US", 2, 3); // American English
- addHyphenator("es", 2, 2); // Spanish
- addHyphenator("et", 2, 3); // Estonian
- addHyphenator("eu", 2, 2); // Basque
- addHyphenator("fr", 2, 3); // French
- addHyphenator("ga", 2, 3); // Irish
- addHyphenator("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati
- addHyphenator("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi
- addHyphenator("hr", 2, 2); // Croatian
- addHyphenator("hu", 2, 2); // Hungarian
+ addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese
+ addHyphenator("be", 2, 2); // Belarusian
+ addHyphenator("bg", 2, 2); // Bulgarian
+ addHyphenator("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali
+ addHyphenator("cu", 1, 2); // Church Slavonic
+ addHyphenator("cy", 2, 3); // Welsh
+ addHyphenator("da", 2, 2); // Danish
+ addHyphenator("de-1901", 2, 2); // German 1901 orthography
+ addHyphenator("de-1996", 2, 2); // German 1996 orthography
+ addHyphenator("de-CH-1901", 2, 2); // Swiss High German 1901 orthography
+ addHyphenator("en-GB", 2, 3); // British English
+ addHyphenator("en-US", 2, 3); // American English
+ addHyphenator("es", 2, 2); // Spanish
+ addHyphenator("et", 2, 3); // Estonian
+ addHyphenator("eu", 2, 2); // Basque
+ addHyphenator("fr", 2, 3); // French
+ addHyphenator("ga", 2, 3); // Irish
+ addHyphenator("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati
+ addHyphenator("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi
+ addHyphenator("hr", 2, 2); // Croatian
+ addHyphenator("hu", 2, 2); // Hungarian
// texhyphen sources say Armenian may be (1, 2); but that it needs confirmation.
// Going with a more conservative value of (2, 2) for now.
- addHyphenator("hy", 2, 2); // Armenian
- addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada
- addHyphenator("la", 2, 2); // Latin
- addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam
- addHyphenator("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script
- addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi
- addHyphenator("nb", 2, 2); // Norwegian Bokmål
- addHyphenator("nn", 2, 2); // Norwegian Nynorsk
- addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya
- addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi
- addHyphenator("pt", 2, 3); // Portuguese
- addHyphenator("sl", 2, 2); // Slovenian
- addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil
- addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu
- addHyphenator("tk", 2, 2); // Turkmen
- addHyphenator("und-Ethi", 1, 1); // Any language in Ethiopic script
+ addHyphenator("hy", 2, 2); // Armenian
+ addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada
+ addHyphenator("la", 2, 2); // Latin
+ addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam
+ addHyphenator("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script
+ addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi
+ addHyphenator("nb", 2, 2); // Norwegian Bokmål
+ addHyphenator("nn", 2, 2); // Norwegian Nynorsk
+ addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya
+ addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi
+ addHyphenator("pt", 2, 3); // Portuguese
+ addHyphenator("sl", 2, 2); // Slovenian
+ addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil
+ addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu
+ addHyphenator("tk", 2, 2); // Turkmen
+ addHyphenator("und-Ethi", 1, 1); // Any language in Ethiopic script
// Following two hyphenators do not have pattern files but there is some special logic based on
// language.
@@ -130,13 +130,13 @@
// English locales that fall back to en-US. The data is from CLDR. It's all English locales,
// minus the locales whose parent is en-001 (from supplementalData.xml, under <parentLocales>).
// TODO: Figure out how to get this from ICU.
- addHyphenatorAlias("en-AS", "en-US"); // English (American Samoa)
- addHyphenatorAlias("en-GU", "en-US"); // English (Guam)
- addHyphenatorAlias("en-MH", "en-US"); // English (Marshall Islands)
- addHyphenatorAlias("en-MP", "en-US"); // English (Northern Mariana Islands)
- addHyphenatorAlias("en-PR", "en-US"); // English (Puerto Rico)
- addHyphenatorAlias("en-UM", "en-US"); // English (United States Minor Outlying Islands)
- addHyphenatorAlias("en-VI", "en-US"); // English (Virgin Islands)
+ addHyphenatorAlias("en-AS", "en-US"); // English (American Samoa)
+ addHyphenatorAlias("en-GU", "en-US"); // English (Guam)
+ addHyphenatorAlias("en-MH", "en-US"); // English (Marshall Islands)
+ addHyphenatorAlias("en-MP", "en-US"); // English (Northern Mariana Islands)
+ addHyphenatorAlias("en-PR", "en-US"); // English (Puerto Rico)
+ addHyphenatorAlias("en-UM", "en-US"); // English (United States Minor Outlying Islands)
+ addHyphenatorAlias("en-VI", "en-US"); // English (Virgin Islands)
// All English locales other than those falling back to en-US are mapped to en-GB.
addHyphenatorAlias("en", "en-GB");
@@ -150,17 +150,28 @@
addHyphenatorAlias("no", "nb");
// Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
- addHyphenatorAlias("mn", "mn-Cyrl"); // Mongolian
+ addHyphenatorAlias("mn", "mn-Cyrl"); // Mongolian
// Fall back to Ethiopic script for languages likely to be written in Ethiopic.
// Data is from CLDR's likelySubtags.xml.
// TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
- addHyphenatorAlias("am", "und-Ethi"); // Amharic
- addHyphenatorAlias("byn", "und-Ethi"); // Blin
- addHyphenatorAlias("gez", "und-Ethi"); // Geʻez
- addHyphenatorAlias("ti", "und-Ethi"); // Tigrinya
- addHyphenatorAlias("wal", "und-Ethi"); // Wolaytta
+ addHyphenatorAlias("am", "und-Ethi"); // Amharic
+ addHyphenatorAlias("byn", "und-Ethi"); // Blin
+ addHyphenatorAlias("gez", "und-Ethi"); // Geʻez
+ addHyphenatorAlias("ti", "und-Ethi"); // Tigrinya
+ addHyphenatorAlias("wal", "und-Ethi"); // Wolaytta
+ // Use Hindi as a fallback hyphenator for all languages written in Devanagari, etc. This makes
+ // sense because our Indic patterns are not really linguistic, but script-based.
+ addHyphenatorAlias("und-Beng", "bn"); // Bengali
+ addHyphenatorAlias("und-Deva", "hi"); // Devanagari -> Hindi
+ addHyphenatorAlias("und-Gujr", "gu"); // Gujarati
+ addHyphenatorAlias("und-Guru", "pa"); // Gurmukhi -> Punjabi
+ addHyphenatorAlias("und-Knda", "kn"); // Kannada
+ addHyphenatorAlias("und-Mlym", "ml"); // Malayalam
+ addHyphenatorAlias("und-Orya", "or"); // Oriya
+ addHyphenatorAlias("und-Taml", "ta"); // Tamil
+ addHyphenatorAlias("und-Telu", "te"); // Telugu
}
static const JNINativeMethod gMethods[] = {
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 04e9dfd..c1419ba 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -55,11 +55,11 @@
class JNILineBreakerLineWidth : public minikin::LineBreaker::LineWidthDelegate {
public:
JNILineBreakerLineWidth(float firstWidth, int32_t firstLineCount, float restWidth,
- std::vector<float>&& indents, std::vector<float>&& leftPaddings,
- std::vector<float>&& rightPaddings, int32_t indentsAndPaddingsOffset)
+ const std::vector<float>& indents, const std::vector<float>& leftPaddings,
+ const std::vector<float>& rightPaddings, int32_t indentsAndPaddingsOffset)
: mFirstWidth(firstWidth), mFirstLineCount(firstLineCount), mRestWidth(restWidth),
- mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)),
- mRightPaddings(std::move(rightPaddings)), mOffset(indentsAndPaddingsOffset) {}
+ mIndents(indents), mLeftPaddings(leftPaddings),
+ mRightPaddings(rightPaddings), mOffset(indentsAndPaddingsOffset) {}
float getLineWidth(size_t lineNo) override {
const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount)
@@ -91,9 +91,9 @@
const float mFirstWidth;
const int32_t mFirstLineCount;
const float mRestWidth;
- const std::vector<float> mIndents;
- const std::vector<float> mLeftPaddings;
- const std::vector<float> mRightPaddings;
+ const std::vector<float>& mIndents;
+ const std::vector<float>& mLeftPaddings;
+ const std::vector<float>& mRightPaddings;
const int32_t mOffset;
};
@@ -106,32 +106,132 @@
}
}
+class Run {
+ public:
+ Run(int32_t start, int32_t end) : mStart(start), mEnd(end) {}
+ virtual ~Run() {}
+
+ virtual void addTo(minikin::LineBreaker* lineBreaker) = 0;
+
+ protected:
+ const int32_t mStart;
+ const int32_t mEnd;
+
+ private:
+ // Forbid copy and assign.
+ Run(const Run&) = delete;
+ void operator=(const Run&) = delete;
+};
+
+class StyleRun : public Run {
+ public:
+ StyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
+ std::shared_ptr<minikin::FontCollection>&& collection,
+ minikin::FontStyle&& style, bool isRtl)
+ : Run(start, end), mPaint(std::move(paint)), mCollection(std::move(collection)),
+ mStyle(std::move(style)), mIsRtl(isRtl) {}
+
+ void addTo(minikin::LineBreaker* lineBreaker) override {
+ lineBreaker->addStyleRun(&mPaint, mCollection, mStyle, mStart, mEnd, mIsRtl);
+ }
+
+ private:
+ minikin::MinikinPaint mPaint;
+ std::shared_ptr<minikin::FontCollection> mCollection;
+ minikin::FontStyle mStyle;
+ const bool mIsRtl;
+};
+
+class Replacement : public Run {
+ public:
+ Replacement(int32_t start, int32_t end, float width, uint32_t localeListId)
+ : Run(start, end), mWidth(width), mLocaleListId(localeListId) {}
+
+ void addTo(minikin::LineBreaker* lineBreaker) override {
+ lineBreaker->addReplacement(mStart, mEnd, mWidth, mLocaleListId);
+ }
+
+ private:
+ const float mWidth;
+ const uint32_t mLocaleListId;
+};
+
+class StaticLayoutNative {
+ public:
+ StaticLayoutNative(
+ minikin::BreakStrategy strategy, minikin::HyphenationFrequency frequency,
+ bool isJustified, std::vector<float>&& indents, std::vector<float>&& leftPaddings,
+ std::vector<float>&& rightPaddings)
+ : mStrategy(strategy), mFrequency(frequency), mIsJustified(isJustified),
+ mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)),
+ mRightPaddings(std::move(rightPaddings)) {}
+
+ void addStyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
+ std::shared_ptr<minikin::FontCollection> collection,
+ minikin::FontStyle&& style, bool isRtl) {
+ mRuns.emplace_back(std::make_unique<StyleRun>(
+ start, end, std::move(paint), std::move(collection), std::move(style), isRtl));
+ }
+
+ void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) {
+ mRuns.emplace_back(std::make_unique<Replacement>(start, end, width, localeListId));
+ }
+
+ // Only valid while this instance is alive.
+ inline std::unique_ptr<minikin::LineBreaker::LineWidthDelegate> buildLineWidthDelegate(
+ float firstWidth, int32_t firstLineCount, float restWidth,
+ int32_t indentsAndPaddingsOffset) {
+ return std::make_unique<JNILineBreakerLineWidth>(
+ firstWidth, firstLineCount, restWidth, mIndents, mLeftPaddings, mRightPaddings,
+ indentsAndPaddingsOffset);
+ }
+
+ void addRuns(minikin::LineBreaker* lineBreaker) {
+ for (const auto& run : mRuns) {
+ run->addTo(lineBreaker);
+ }
+ }
+
+ void clearRuns() {
+ mRuns.clear();
+ }
+
+ inline minikin::BreakStrategy getStrategy() const { return mStrategy; }
+ inline minikin::HyphenationFrequency getFrequency() const { return mFrequency; }
+ inline bool isJustified() const { return mIsJustified; }
+
+ private:
+ const minikin::BreakStrategy mStrategy;
+ const minikin::HyphenationFrequency mFrequency;
+ const bool mIsJustified;
+ const std::vector<float> mIndents;
+ const std::vector<float> mLeftPaddings;
+ const std::vector<float> mRightPaddings;
+
+ std::vector<std::unique_ptr<Run>> mRuns;
+};
+
+static inline StaticLayoutNative* toNative(jlong ptr) {
+ return reinterpret_cast<StaticLayoutNative*>(ptr);
+}
+
// set text and set a number of parameters for creating a layout (width, tabstops, strategy,
// hyphenFrequency)
-static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length,
- jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
- jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency,
- jboolean isJustified, jintArray indents, jintArray leftPaddings, jintArray rightPaddings,
- jint indentsAndPaddingsOffset) {
- minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
- b->resize(length);
- env->GetCharArrayRegion(text, 0, length, b->buffer());
- b->setText();
- if (variableTabStops == nullptr) {
- b->setTabStops(nullptr, 0, defaultTabStop);
- } else {
- ScopedIntArrayRO stops(env, variableTabStops);
- b->setTabStops(stops.get(), stops.size(), defaultTabStop);
- }
- b->setStrategy(static_cast<minikin::BreakStrategy>(strategy));
- b->setHyphenationFrequency(static_cast<minikin::HyphenationFrequency>(hyphenFrequency));
- b->setJustified(isJustified);
+static jlong nInit(JNIEnv* env, jclass /* unused */,
+ jint breakStrategy, jint hyphenationFrequency, jboolean isJustified,
+ jintArray indents, jintArray leftPaddings, jintArray rightPaddings) {
+ return reinterpret_cast<jlong>(new StaticLayoutNative(
+ static_cast<minikin::BreakStrategy>(breakStrategy),
+ static_cast<minikin::HyphenationFrequency>(hyphenationFrequency),
+ isJustified,
+ jintArrayToFloatVector(env, indents),
+ jintArrayToFloatVector(env, leftPaddings),
+ jintArrayToFloatVector(env, rightPaddings)));
+}
- // TODO: copy indents and paddings only once when LineBreaker is started to be used.
- b->setLineWidthDelegate(std::make_unique<JNILineBreakerLineWidth>(
- firstWidth, firstWidthLineLimit, restWidth, jintArrayToFloatVector(env, indents),
- jintArrayToFloatVector(env, leftPaddings), jintArrayToFloatVector(env, rightPaddings),
- indentsAndPaddingsOffset));
+// CriticalNative
+static void nFinish(jlong nativePtr) {
+ delete toNative(nativePtr);
}
static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
@@ -163,42 +263,65 @@
}
static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
- jobject recycle, jintArray recycleBreaks,
- jfloatArray recycleWidths, jfloatArray recycleAscents,
- jfloatArray recycleDescents, jintArray recycleFlags,
- jint recycleLength, jfloatArray charWidths) {
- minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
+ // Inputs
+ jcharArray text,
+ jint length,
+ jfloat firstWidth,
+ jint firstWidthLineCount,
+ jfloat restWidth,
+ jintArray variableTabStops,
+ jint defaultTabStop,
+ jint indentsOffset,
- size_t nBreaks = b->computeBreaks();
+ // Outputs
+ jobject recycle,
+ jint recycleLength,
+ jintArray recycleBreaks,
+ jfloatArray recycleWidths,
+ jfloatArray recycleAscents,
+ jfloatArray recycleDescents,
+ jintArray recycleFlags,
+ jfloatArray charWidths) {
+
+ StaticLayoutNative* builder = toNative(nativePtr);
+
+ // TODO: Reorganize minikin APIs.
+ minikin::LineBreaker b;
+ b.resize(length);
+ env->GetCharArrayRegion(text, 0, length, b.buffer());
+ b.setText();
+ if (variableTabStops == nullptr) {
+ b.setTabStops(nullptr, 0, defaultTabStop);
+ } else {
+ ScopedIntArrayRO stops(env, variableTabStops);
+ b.setTabStops(stops.get(), stops.size(), defaultTabStop);
+ }
+ b.setStrategy(builder->getStrategy());
+ b.setHyphenationFrequency(builder->getFrequency());
+ b.setJustified(builder->isJustified());
+ b.setLineWidthDelegate(builder->buildLineWidthDelegate(
+ firstWidth, firstWidthLineCount, restWidth, indentsOffset));
+
+ builder->addRuns(&b);
+
+ size_t nBreaks = b.computeBreaks();
recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents,
- recycleFlags, recycleLength, nBreaks, b->getBreaks(), b->getWidths(), b->getAscents(),
- b->getDescents(), b->getFlags());
+ recycleFlags, recycleLength, nBreaks, b.getBreaks(), b.getWidths(), b.getAscents(),
+ b.getDescents(), b.getFlags());
- env->SetFloatArrayRegion(charWidths, 0, b->size(), b->charWidths());
+ env->SetFloatArrayRegion(charWidths, 0, b.size(), b.charWidths());
- b->finish();
+ b.finish();
+ builder->clearRuns();
return static_cast<jint>(nBreaks);
}
-static jlong nNewBuilder(JNIEnv*, jclass) {
- return reinterpret_cast<jlong>(new minikin::LineBreaker);
-}
-
-static void nFreeBuilder(JNIEnv*, jclass, jlong nativePtr) {
- delete reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-}
-
-static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) {
- minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
- b->finish();
-}
-
// Basically similar to Paint.getTextRunAdvances but with C++ interface
-static void nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jint start,
- jint end, jboolean isRtl) {
- minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
+// CriticalNative
+static void nAddStyleRun(jlong nativePtr, jlong nativePaint, jint start, jint end, jboolean isRtl) {
+ StaticLayoutNative* builder = toNative(nativePtr);
Paint* paint = reinterpret_cast<Paint*>(nativePaint);
const Typeface* typeface = paint->getAndroidTypeface();
minikin::MinikinPaint minikinPaint;
@@ -206,26 +329,59 @@
minikin::FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, paint,
typeface);
- b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start, end, isRtl);
+ builder->addStyleRun(
+ start, end, std::move(minikinPaint), resolvedTypeface->fFontCollection, std::move(style),
+ isRtl);
}
-static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint,
- jint start, jint end, jfloat width) {
- minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
+// CriticalNative
+static void nAddReplacementRun(jlong nativePtr, jlong nativePaint, jint start, jint end,
+ jfloat width) {
+ StaticLayoutNative* builder = toNative(nativePtr);
Paint* paint = reinterpret_cast<Paint*>(nativePaint);
- b->addReplacement(start, end, width, paint->getMinikinLangListId());
+ builder->addReplacementRun(start, end, width, paint->getMinikinLangListId());
}
static const JNINativeMethod gMethods[] = {
- // TODO performance: many of these are candidates for fast jni, awaiting guidance
- {"nNewBuilder", "()J", (void*) nNewBuilder},
- {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
- {"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
- {"nSetupParagraph", "(J[CIFIF[IIIIZ[I[I[II)V", (void*) nSetupParagraph},
+ // Fast Natives
+ {"nInit", "("
+ "I" // breakStrategy
+ "I" // hyphenationFrequency
+ "Z" // isJustified
+ "[I" // indents
+ "[I" // left paddings
+ "[I" // right paddings
+ ")J", (void*) nInit},
+
+ // Critical Natives
+ {"nFinish", "(J)V", (void*) nFinish},
{"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
- {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[F[F[II[F)I",
- (void*) nComputeLineBreaks}
+
+ // Regular JNI
+ {"nComputeLineBreaks", "("
+ "J" // nativePtr
+
+ // Inputs
+ "[C" // text
+ "I" // length
+ "F" // firstWidth
+ "I" // firstWidthLineCount
+ "F" // restWidth
+ "[I" // variableTabStops
+ "I" // defaultTabStop
+ "I" // indentsOffset
+
+ // Outputs
+ "Landroid/text/StaticLayout$LineBreaks;" // recycle
+ "I" // recycleLength
+ "[I" // recycleBreaks
+ "[F" // recycleWidths
+ "[F" // recycleAscents
+ "[F" // recycleDescents
+ "[I" // recycleFlags
+ "[F" // charWidths
+ ")I", (void*) nComputeLineBreaks}
};
int register_android_text_StaticLayout(JNIEnv* env)
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index b137da3..c6828c4 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -568,6 +568,35 @@
return (res) ? (jint)cookie : 0;
}
+static jint android_content_AssetManager_addAssetFd(JNIEnv* env, jobject clazz,
+ jobject fileDescriptor, jstring debugPathName,
+ jboolean appAsLib)
+{
+ ScopedUtfChars debugPathName8(env, debugPathName);
+
+ int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+ if (fd < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+ return 0;
+ }
+
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return 0;
+ }
+
+ int dupfd = ::dup(fd);
+ if (dupfd < 0) {
+ jniThrowIOException(env, errno);
+ return 0;
+ }
+
+ int32_t cookie;
+ bool res = am->addAssetFd(dupfd, String8(debugPathName8.c_str()), &cookie, appAsLib);
+
+ return (res) ? static_cast<jint>(cookie) : 0;
+}
+
static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject clazz)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
@@ -1673,6 +1702,8 @@
(void*) android_content_AssetManager_getAssetRemainingLength },
{ "addAssetPathNative", "(Ljava/lang/String;Z)I",
(void*) android_content_AssetManager_addAssetPath },
+ { "addAssetFdNative", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)I",
+ (void*) android_content_AssetManager_addAssetFd },
{ "addOverlayPathNative", "(Ljava/lang/String;)I",
(void*) android_content_AssetManager_addOverlayPath },
{ "isUpToDate", "()Z",
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 7908c9d..dea38e8 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -20,6 +20,7 @@
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
+#include <atomic>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
@@ -32,6 +33,7 @@
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <binder/Parcel.h>
+#include <binder/BpBinder.h>
#include <binder/ProcessState.h>
#include <log/log.h>
#include <utils/Atomic.h>
@@ -80,9 +82,17 @@
// Class state.
jclass mClass;
jmethodID mForceGc;
+ jmethodID mProxyLimitCallback;
} gBinderInternalOffsets;
+static struct sparseintarray_offsets_t
+{
+ jclass classObject;
+ jmethodID constructor;
+ jmethodID put;
+} gSparseIntArrayOffsets;
+
// ----------------------------------------------------------------------------
static struct error_offsets_t
@@ -96,14 +106,11 @@
{
// Class state.
jclass mClass;
- jmethodID mConstructor;
+ jmethodID mGetInstance;
jmethodID mSendDeathNotice;
// Object state.
- jfieldID mObject;
- jfieldID mSelf;
- jfieldID mOrgue;
-
+ jfieldID mNativeData; // Field holds native pointer to BinderProxyNativeData.
} gBinderProxyOffsets;
static struct class_offsets_t
@@ -144,20 +151,45 @@
// ****************************************************************************
// ****************************************************************************
-static volatile int32_t gNumRefsCreated = 0;
-static volatile int32_t gNumProxyRefs = 0;
-static volatile int32_t gNumLocalRefs = 0;
-static volatile int32_t gNumDeathRefs = 0;
+static constexpr int32_t PROXY_WARN_INTERVAL = 5000;
+static constexpr uint32_t GC_INTERVAL = 1000;
-static void incRefsCreated(JNIEnv* env)
+// Protected by gProxyLock. We warn if this gets too large.
+static int32_t gNumProxies = 0;
+static int32_t gProxiesWarned = 0;
+
+// Number of GlobalRefs held by JavaBBinders.
+static std::atomic<uint32_t> gNumLocalRefsCreated(0);
+static std::atomic<uint32_t> gNumLocalRefsDeleted(0);
+// Number of GlobalRefs held by JavaDeathRecipients.
+static std::atomic<uint32_t> gNumDeathRefsCreated(0);
+static std::atomic<uint32_t> gNumDeathRefsDeleted(0);
+
+// We collected after creating this many refs.
+static std::atomic<uint32_t> gCollectedAtRefs(0);
+
+// Garbage collect if we've allocated at least GC_INTERVAL refs since the last time.
+// TODO: Consider removing this completely. We should no longer be generating GlobalRefs
+// that are reclaimed as a result of GC action.
+static void gcIfManyNewRefs(JNIEnv* env)
{
- int old = android_atomic_inc(&gNumRefsCreated);
- if (old == 200) {
- android_atomic_and(0, &gNumRefsCreated);
- env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
- gBinderInternalOffsets.mForceGc);
+ uint32_t totalRefs = gNumLocalRefsCreated.load(std::memory_order_relaxed)
+ + gNumDeathRefsCreated.load(std::memory_order_relaxed);
+ uint32_t collectedAtRefs = gCollectedAtRefs.load(memory_order_relaxed);
+ // A bound on the number of threads that can have incremented gNum...RefsCreated before the
+ // following check is executed. Effectively a bound on #threads. Almost any value will do.
+ static constexpr uint32_t MAX_RACING = 100000;
+
+ if (totalRefs - (collectedAtRefs + GC_INTERVAL) /* modular arithmetic! */ < MAX_RACING) {
+ // Recently passed next GC interval.
+ if (gCollectedAtRefs.compare_exchange_strong(collectedAtRefs,
+ collectedAtRefs + GC_INTERVAL, std::memory_order_relaxed)) {
+ ALOGV("Binder forcing GC at %u created refs", totalRefs);
+ env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
+ gBinderInternalOffsets.mForceGc);
+ } // otherwise somebody else beat us to it.
} else {
- ALOGV("Now have %d binder ops", old);
+ ALOGV("Now have %d binder ops", totalRefs - collectedAtRefs);
}
}
@@ -267,12 +299,12 @@
class JavaBBinder : public BBinder
{
public:
- JavaBBinder(JNIEnv* env, jobject object)
+ JavaBBinder(JNIEnv* env, jobject /* Java Binder */ object)
: mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object))
{
ALOGV("Creating JavaBBinder %p\n", this);
- android_atomic_inc(&gNumLocalRefs);
- incRefsCreated(env);
+ gNumLocalRefsCreated.fetch_add(1, std::memory_order_relaxed);
+ gcIfManyNewRefs(env);
}
bool checkSubclass(const void* subclassID) const
@@ -289,7 +321,7 @@
virtual ~JavaBBinder()
{
ALOGV("Destroying JavaBBinder %p\n", this);
- android_atomic_dec(&gNumLocalRefs);
+ gNumLocalRefsDeleted.fetch_add(1, memory_order_relaxed);
JNIEnv* env = javavm_to_jnienv(mVM);
env->DeleteGlobalRef(mObject);
}
@@ -351,12 +383,12 @@
private:
JavaVM* const mVM;
- jobject const mObject;
+ jobject const mObject; // GlobalRef to Java Binder
};
// ----------------------------------------------------------------------------
-class JavaBBinderHolder : public RefBase
+class JavaBBinderHolder
{
public:
sp<JavaBBinder> get(JNIEnv* env, jobject obj)
@@ -421,8 +453,8 @@
LOGDEATH("Adding JDR %p to DRL %p", this, list.get());
list->add(this);
- android_atomic_inc(&gNumDeathRefs);
- incRefsCreated(env);
+ gNumDeathRefsCreated.fetch_add(1, std::memory_order_relaxed);
+ gcIfManyNewRefs(env);
}
void binderDied(const wp<IBinder>& who)
@@ -502,7 +534,7 @@
virtual ~JavaDeathRecipient()
{
//ALOGI("Removing death ref: recipient=%p\n", mObject);
- android_atomic_dec(&gNumDeathRefs);
+ gNumDeathRefsDeleted.fetch_add(1, std::memory_order_relaxed);
JNIEnv* env = javavm_to_jnienv(mVM);
if (mObject != NULL) {
env->DeleteGlobalRef(mObject);
@@ -514,7 +546,7 @@
private:
JavaVM* const mVM;
jobject mObject; // Initial strong ref to Java-side DeathRecipient. Cleared on binderDied().
- jweak mObjectWeak; // weak ref to the same Java-side DeathRecipient after binderDied().
+ jweak mObjectWeak; // Weak ref to the same Java-side DeathRecipient after binderDied().
wp<DeathRecipientList> mList;
};
@@ -579,21 +611,40 @@
namespace android {
-static void proxy_cleanup(const void* id, void* obj, void* cleanupCookie)
-{
- android_atomic_dec(&gNumProxyRefs);
- JNIEnv* env = javavm_to_jnienv((JavaVM*)cleanupCookie);
- env->DeleteGlobalRef((jobject)obj);
+// We aggregate native pointer fields for BinderProxy in a single object to allow
+// management with a single NativeAllocationRegistry, and to reduce the number of JNI
+// Java field accesses. This costs us some extra indirections here.
+struct BinderProxyNativeData {
+ // Both fields are constant and not null once javaObjectForIBinder returns this as
+ // part of a BinderProxy.
+
+ // The native IBinder proxied by this BinderProxy.
+ sp<IBinder> mObject;
+
+ // Death recipients for mObject. Reference counted only because DeathRecipients
+ // hold a weak reference that can be temporarily promoted.
+ sp<DeathRecipientList> mOrgue; // Death recipients for mObject.
+};
+
+BinderProxyNativeData* getBPNativeData(JNIEnv* env, jobject obj) {
+ return (BinderProxyNativeData *) env->GetLongField(obj, gBinderProxyOffsets.mNativeData);
}
static Mutex gProxyLock;
+// We may cache a single BinderProxyNativeData node to avoid repeat allocation.
+// All fields are null. Protected by gProxyLock.
+static BinderProxyNativeData *gNativeDataCache;
+
+// If the argument is a JavaBBinder, return the Java object that was used to create it.
+// Otherwise return a BinderProxy for the IBinder. If a previous call was passed the
+// same IBinder, and the original BinderProxy is still alive, return the same BinderProxy.
jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
{
if (val == NULL) return NULL;
if (val->checkSubclass(&gBinderOffsets)) {
- // One of our own!
+ // It's a JavaBBinder created by ibinderForJavaObject. Already has Java object.
jobject object = static_cast<JavaBBinder*>(val.get())->object();
LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object);
return object;
@@ -603,42 +654,31 @@
// looking/creation/destruction of Java proxies for native Binder proxies.
AutoMutex _l(gProxyLock);
- // Someone else's... do we know about it?
- jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
- if (object != NULL) {
- jobject res = jniGetReferent(env, object);
- if (res != NULL) {
- ALOGV("objectForBinder %p: found existing %p!\n", val.get(), res);
- return res;
- }
- LOGDEATH("Proxy object %p of IBinder %p no longer in working set!!!", object, val.get());
- android_atomic_dec(&gNumProxyRefs);
- val->detachObject(&gBinderProxyOffsets);
- env->DeleteGlobalRef(object);
+ BinderProxyNativeData* nativeData = gNativeDataCache;
+ if (nativeData == nullptr) {
+ nativeData = new BinderProxyNativeData();
}
-
- object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
- if (object != NULL) {
- LOGDEATH("objectForBinder %p: created new proxy %p !\n", val.get(), object);
- // The proxy holds a reference to the native object.
- env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
- val->incStrong((void*)javaObjectForIBinder);
-
- // The native object needs to hold a weak reference back to the
- // proxy, so we can retrieve the same proxy if it is still active.
- jobject refObject = env->NewGlobalRef(
- env->GetObjectField(object, gBinderProxyOffsets.mSelf));
- val->attachObject(&gBinderProxyOffsets, refObject,
- jnienv_to_javavm(env), proxy_cleanup);
-
- // Also remember the death recipients registered on this proxy
- sp<DeathRecipientList> drl = new DeathRecipientList;
- drl->incStrong((void*)javaObjectForIBinder);
- env->SetLongField(object, gBinderProxyOffsets.mOrgue, reinterpret_cast<jlong>(drl.get()));
-
- // Note that a new object reference has been created.
- android_atomic_inc(&gNumProxyRefs);
- incRefsCreated(env);
+ // gNativeDataCache is now logically empty.
+ jobject object = env->CallStaticObjectMethod(gBinderProxyOffsets.mClass,
+ gBinderProxyOffsets.mGetInstance, (jlong) nativeData, (jlong) val.get());
+ if (env->ExceptionCheck()) {
+ gNativeDataCache = nativeData;
+ return NULL;
+ }
+ BinderProxyNativeData* actualNativeData = getBPNativeData(env, object);
+ if (actualNativeData == nativeData) {
+ // New BinderProxy; we still have exclusive access.
+ nativeData->mOrgue = new DeathRecipientList;
+ nativeData->mObject = val;
+ gNativeDataCache = nullptr;
+ ++gNumProxies;
+ if (++gNumProxies >= gProxiesWarned + PROXY_WARN_INTERVAL) {
+ ALOGW("Unexpectedly many live BinderProxies: %d\n", gNumProxies);
+ gProxiesWarned = gNumProxies;
+ }
+ } else {
+ // nativeData wasn't used. Reuse it the next time.
+ gNativeDataCache = nativeData;
}
return object;
@@ -648,15 +688,16 @@
{
if (obj == NULL) return NULL;
+ // Instance of Binder?
if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) {
JavaBBinderHolder* jbh = (JavaBBinderHolder*)
env->GetLongField(obj, gBinderOffsets.mObject);
- return jbh != NULL ? jbh->get(env, obj) : NULL;
+ return jbh->get(env, obj);
}
+ // Instance of BinderProxy?
if (env->IsInstanceOf(obj, gBinderProxyOffsets.mClass)) {
- return (IBinder*)
- env->GetLongField(obj, gBinderProxyOffsets.mObject);
+ return getBPNativeData(env, obj)->mObject;
}
ALOGW("ibinderForJavaObject: %p is not a Binder object", obj);
@@ -849,35 +890,21 @@
IPCThreadState::self()->flushCommands();
}
-static void android_os_Binder_init(JNIEnv* env, jobject obj)
+static jlong android_os_Binder_getNativeBBinderHolder(JNIEnv* env, jobject clazz)
{
JavaBBinderHolder* jbh = new JavaBBinderHolder();
- if (jbh == NULL) {
- jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
- return;
- }
- ALOGV("Java Binder %p: acquiring first ref on holder %p", obj, jbh);
- jbh->incStrong((void*)android_os_Binder_init);
- env->SetLongField(obj, gBinderOffsets.mObject, (jlong)jbh);
+ return (jlong) jbh;
}
-static void android_os_Binder_destroyBinder(JNIEnv* env, jobject obj)
+static void Binder_destroy(void* rawJbh)
{
- JavaBBinderHolder* jbh = (JavaBBinderHolder*)
- env->GetLongField(obj, gBinderOffsets.mObject);
- if (jbh != NULL) {
- env->SetLongField(obj, gBinderOffsets.mObject, 0);
- ALOGV("Java Binder %p: removing ref on holder %p", obj, jbh);
- jbh->decStrong((void*)android_os_Binder_init);
- } else {
- // Encountering an uninitialized binder is harmless. All it means is that
- // the Binder was only partially initialized when its finalizer ran and called
- // destroyBinder(). The Binder could be partially initialized for several reasons.
- // For example, a Binder subclass constructor might have thrown an exception before
- // it could delegate to its superclass's constructor. Consequently init() would
- // not have been called and the holder pointer would remain NULL.
- ALOGV("Java Binder %p: ignoring uninitialized binder", obj);
- }
+ JavaBBinderHolder* jbh = (JavaBBinderHolder*) rawJbh;
+ ALOGV("Java Binder: deleting holder %p", jbh);
+ delete jbh;
+}
+
+JNIEXPORT jlong JNICALL android_os_Binder_getNativeFinalizer(JNIEnv*, jclass) {
+ return (jlong) Binder_destroy;
}
static void android_os_Binder_blockUntilThreadAvailable(JNIEnv* env, jobject clazz)
@@ -896,8 +923,8 @@
{ "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy },
{ "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy },
{ "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
- { "init", "()V", (void*)android_os_Binder_init },
- { "destroyBinder", "()V", (void*)android_os_Binder_destroyBinder },
+ { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
+ { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
{ "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }
};
@@ -924,17 +951,18 @@
jint android_os_Debug_getLocalObjectCount(JNIEnv* env, jobject clazz)
{
- return gNumLocalRefs;
+ return gNumLocalRefsCreated - gNumLocalRefsDeleted;
}
jint android_os_Debug_getProxyObjectCount(JNIEnv* env, jobject clazz)
{
- return gNumProxyRefs;
+ AutoMutex _l(gProxyLock);
+ return gNumProxies;
}
jint android_os_Debug_getDeathObjectCount(JNIEnv* env, jobject clazz)
{
- return gNumDeathRefs;
+ return gNumDeathRefsCreated - gNumDeathRefsDeleted;
}
}
@@ -969,8 +997,45 @@
static void android_os_BinderInternal_handleGc(JNIEnv* env, jobject clazz)
{
- ALOGV("Gc has executed, clearing binder ops");
- android_atomic_and(0, &gNumRefsCreated);
+ ALOGV("Gc has executed, updating Refs count at GC");
+ gCollectedAtRefs = gNumLocalRefsCreated + gNumDeathRefsCreated;
+}
+
+static void android_os_BinderInternal_proxyLimitcallback(int uid)
+{
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
+ gBinderInternalOffsets.mProxyLimitCallback,
+ uid);
+}
+
+static void android_os_BinderInternal_setBinderProxyCountEnabled(JNIEnv* env, jobject clazz,
+ jboolean enable)
+{
+ BpBinder::setCountByUidEnabled((bool) enable);
+}
+
+static jobject android_os_BinderInternal_getBinderProxyPerUidCounts(JNIEnv* env, jclass clazz)
+{
+ Vector<uint32_t> uids, counts;
+ BpBinder::getCountByUid(uids, counts);
+ jobject sparseIntArray = env->NewObject(gSparseIntArrayOffsets.classObject,
+ gSparseIntArrayOffsets.constructor);
+ for (size_t i = 0; i < uids.size(); i++) {
+ env->CallVoidMethod(sparseIntArray, gSparseIntArrayOffsets.put,
+ static_cast<jint>(uids[i]), static_cast<jint>(counts[i]));
+ }
+ return sparseIntArray;
+}
+
+static jint android_os_BinderInternal_getBinderProxyCount(JNIEnv* env, jobject clazz, jint uid) {
+ return static_cast<jint>(BpBinder::getBinderProxyCount(static_cast<uint32_t>(uid)));
+}
+
+static void android_os_BinderInternal_setBinderProxyCountWatermarks(JNIEnv* env, jobject clazz,
+ jint high, jint low)
+{
+ BpBinder::setBinderProxyCountWatermarks(high, low);
}
// ----------------------------------------------------------------------------
@@ -981,7 +1046,11 @@
{ "joinThreadPool", "()V", (void*)android_os_BinderInternal_joinThreadPool },
{ "disableBackgroundScheduling", "(Z)V", (void*)android_os_BinderInternal_disableBackgroundScheduling },
{ "setMaxThreads", "(I)V", (void*)android_os_BinderInternal_setMaxThreads },
- { "handleGc", "()V", (void*)android_os_BinderInternal_handleGc }
+ { "handleGc", "()V", (void*)android_os_BinderInternal_handleGc },
+ { "nSetBinderProxyCountEnabled", "(Z)V", (void*)android_os_BinderInternal_setBinderProxyCountEnabled },
+ { "nGetBinderProxyPerUidCounts", "()Landroid/util/SparseIntArray;", (void*)android_os_BinderInternal_getBinderProxyPerUidCounts },
+ { "nGetBinderProxyCount", "(I)I", (void*)android_os_BinderInternal_getBinderProxyCount },
+ { "nSetBinderProxyCountWatermarks", "(II)V", (void*)android_os_BinderInternal_setBinderProxyCountWatermarks}
};
const char* const kBinderInternalPathName = "com/android/internal/os/BinderInternal";
@@ -992,6 +1061,16 @@
gBinderInternalOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
gBinderInternalOffsets.mForceGc = GetStaticMethodIDOrDie(env, clazz, "forceBinderGc", "()V");
+ gBinderInternalOffsets.mProxyLimitCallback = GetStaticMethodIDOrDie(env, clazz, "binderProxyLimitCallbackFromNative", "(I)V");
+
+ jclass SparseIntArrayClass = FindClassOrDie(env, "android/util/SparseIntArray");
+ gSparseIntArrayOffsets.classObject = MakeGlobalRefOrDie(env, SparseIntArrayClass);
+ gSparseIntArrayOffsets.constructor = GetMethodIDOrDie(env, gSparseIntArrayOffsets.classObject,
+ "<init>", "()V");
+ gSparseIntArrayOffsets.put = GetMethodIDOrDie(env, gSparseIntArrayOffsets.classObject, "put",
+ "(II)V");
+
+ BpBinder::setLimitCallback(android_os_BinderInternal_proxyLimitcallback);
return RegisterMethodsOrDie(
env, kBinderInternalPathName,
@@ -1004,8 +1083,7 @@
static jboolean android_os_BinderProxy_pingBinder(JNIEnv* env, jobject obj)
{
- IBinder* target = (IBinder*)
- env->GetLongField(obj, gBinderProxyOffsets.mObject);
+ IBinder* target = getBPNativeData(env, obj)->mObject.get();
if (target == NULL) {
return JNI_FALSE;
}
@@ -1015,7 +1093,7 @@
static jstring android_os_BinderProxy_getInterfaceDescriptor(JNIEnv* env, jobject obj)
{
- IBinder* target = (IBinder*) env->GetLongField(obj, gBinderProxyOffsets.mObject);
+ IBinder* target = getBPNativeData(env, obj)->mObject.get();
if (target != NULL) {
const String16& desc = target->getInterfaceDescriptor();
return env->NewString(reinterpret_cast<const jchar*>(desc.string()),
@@ -1028,8 +1106,7 @@
static jboolean android_os_BinderProxy_isBinderAlive(JNIEnv* env, jobject obj)
{
- IBinder* target = (IBinder*)
- env->GetLongField(obj, gBinderProxyOffsets.mObject);
+ IBinder* target = getBPNativeData(env, obj)->mObject.get();
if (target == NULL) {
return JNI_FALSE;
}
@@ -1151,8 +1228,7 @@
return JNI_FALSE;
}
- IBinder* target = (IBinder*)
- env->GetLongField(obj, gBinderProxyOffsets.mObject);
+ IBinder* target = getBPNativeData(env, obj)->mObject.get();
if (target == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", "Binder has been finalized!");
return JNI_FALSE;
@@ -1202,18 +1278,13 @@
return;
}
- IBinder* target = (IBinder*)
- env->GetLongField(obj, gBinderProxyOffsets.mObject);
- if (target == NULL) {
- ALOGW("Binder has been finalized when calling linkToDeath() with recip=%p)\n", recipient);
- assert(false);
- }
+ BinderProxyNativeData *nd = getBPNativeData(env, obj);
+ IBinder* target = nd->mObject.get();
LOGDEATH("linkToDeath: binder=%p recipient=%p\n", target, recipient);
if (!target->localBinder()) {
- DeathRecipientList* list = (DeathRecipientList*)
- env->GetLongField(obj, gBinderProxyOffsets.mOrgue);
+ DeathRecipientList* list = nd->mOrgue.get();
sp<JavaDeathRecipient> jdr = new JavaDeathRecipient(env, recipient, list);
status_t err = target->linkToDeath(jdr, NULL, flags);
if (err != NO_ERROR) {
@@ -1234,8 +1305,8 @@
return res;
}
- IBinder* target = (IBinder*)
- env->GetLongField(obj, gBinderProxyOffsets.mObject);
+ BinderProxyNativeData* nd = getBPNativeData(env, obj);
+ IBinder* target = nd->mObject.get();
if (target == NULL) {
ALOGW("Binder has been finalized when calling linkToDeath() with recip=%p)\n", recipient);
return JNI_FALSE;
@@ -1247,8 +1318,7 @@
status_t err = NAME_NOT_FOUND;
// If we find the matching recipient, proceed to unlink using that
- DeathRecipientList* list = (DeathRecipientList*)
- env->GetLongField(obj, gBinderProxyOffsets.mOrgue);
+ DeathRecipientList* list = nd->mOrgue.get();
sp<JavaDeathRecipient> origJDR = list->find(recipient);
LOGDEATH(" unlink found list %p and JDR %p", list, origJDR.get());
if (origJDR != NULL) {
@@ -1274,25 +1344,21 @@
return res;
}
-static void android_os_BinderProxy_destroy(JNIEnv* env, jobject obj)
+static void BinderProxy_destroy(void* rawNativeData)
{
// Don't race with construction/initialization
AutoMutex _l(gProxyLock);
- IBinder* b = (IBinder*)
- env->GetLongField(obj, gBinderProxyOffsets.mObject);
- DeathRecipientList* drl = (DeathRecipientList*)
- env->GetLongField(obj, gBinderProxyOffsets.mOrgue);
-
- LOGDEATH("Destroying BinderProxy %p: binder=%p drl=%p\n", obj, b, drl);
- if (b != nullptr) {
- env->SetLongField(obj, gBinderProxyOffsets.mObject, 0);
- env->SetLongField(obj, gBinderProxyOffsets.mOrgue, 0);
- drl->decStrong((void*)javaObjectForIBinder);
- b->decStrong((void*)javaObjectForIBinder);
- }
-
+ BinderProxyNativeData * nativeData = (BinderProxyNativeData *) rawNativeData;
+ LOGDEATH("Destroying BinderProxy: binder=%p drl=%p\n",
+ nativeData->mObject.get(), nativeData->mOrgue.get());
+ delete nativeData;
IPCThreadState::self()->flushCommands();
+ --gNumProxies;
+}
+
+JNIEXPORT jlong JNICALL android_os_BinderProxy_getNativeFinalizer(JNIEnv*, jclass) {
+ return (jlong) BinderProxy_destroy;
}
// ----------------------------------------------------------------------------
@@ -1305,7 +1371,7 @@
{"transactNative", "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact},
{"linkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},
{"unlinkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath},
- {"destroy", "()V", (void*)android_os_BinderProxy_destroy},
+ {"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer},
};
const char* const kBinderProxyPathName = "android/os/BinderProxy";
@@ -1317,14 +1383,11 @@
clazz = FindClassOrDie(env, kBinderProxyPathName);
gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
- gBinderProxyOffsets.mConstructor = GetMethodIDOrDie(env, clazz, "<init>", "()V");
+ gBinderProxyOffsets.mGetInstance = GetStaticMethodIDOrDie(env, clazz, "getInstance",
+ "(JJ)Landroid/os/BinderProxy;");
gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
"(Landroid/os/IBinder$DeathRecipient;)V");
-
- gBinderProxyOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
- gBinderProxyOffsets.mSelf = GetFieldIDOrDie(env, clazz, "mSelf",
- "Ljava/lang/ref/WeakReference;");
- gBinderProxyOffsets.mOrgue = GetFieldIDOrDie(env, clazz, "mOrgue", "J");
+ gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J");
clazz = FindClassOrDie(env, "java/lang/Class");
gClassOffsets.mGetName = GetMethodIDOrDie(env, clazz, "getName", "()Ljava/lang/String;");
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 8ae9ada..cfeba83 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -290,6 +290,22 @@
}
}
+static void nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerHandleToken,
+ jobject surfaceObj, int rotation) {
+
+ sp<IBinder> layerHandle = ibinderForJavaObject(env, layerHandleToken);
+ if (layerHandle == NULL) {
+ return;
+ }
+
+ sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj);
+ if (consumer == NULL) {
+ return;
+ }
+
+ ScreenshotClient::captureLayers(layerHandle, consumer->getIGraphicBufferProducer(), rotation);
+}
+
static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
transaction->apply(sync);
@@ -949,6 +965,8 @@
{"nativeScreenshotToBuffer",
"(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZI)Landroid/graphics/GraphicBuffer;",
(void*)nativeScreenshotToBuffer },
+ {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/view/Surface;I)V",
+ (void*)nativeCaptureLayers },
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/proto/android/app/activitymanager.proto b/core/proto/android/app/activitymanager.proto
new file mode 100644
index 0000000..e87499e
--- /dev/null
+++ b/core/proto/android/app/activitymanager.proto
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.app;
+
+option java_multiple_files = true;
+
+message ActivityManagerProto {
+
+ // ActivityManager.java PROCESS_STATEs
+ enum ProcessState {
+ // Order matters for process states, so values have been spaced to provide
+ // room for future additions.
+
+ // Not a real process state.
+ PROCESS_STATE_UNKNOWN = -100;
+ // Process is a persistent system process.
+ PROCESS_STATE_PERSISTENT = 0;
+ // Process is a persistent system process and is doing UI.
+ PROCESS_STATE_PERSISTENT_UI = 100;
+ // Process is hosting the current top activities. Note that this covers
+ // all activities that are visible to the user.
+ PROCESS_STATE_TOP = 200;
+ // Process is hosting a foreground service due to a system binding.
+ PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 300;
+ // Process is hosting a foreground service.
+ PROCESS_STATE_FOREGROUND_SERVICE = 400;
+ // Same as PROCESS_STATE_TOP but while device is sleeping.
+ PROCESS_STATE_TOP_SLEEPING = 500;
+ // Process is important to the user, and something they are aware of.
+ PROCESS_STATE_IMPORTANT_FOREGROUND = 600;
+ // Process is important to the user, but not something they are aware of.
+ PROCESS_STATE_IMPORTANT_BACKGROUND = 700;
+ // Process is in the background transient so we will try to keep running.
+ PROCESS_STATE_TRANSIENT_BACKGROUND = 800;
+ // Process is in the background running a backup/restore operation.
+ PROCESS_STATE_BACKUP = 900;
+ // Process is in the background, but it can't restore its state so we want
+ // to try to avoid killing it.
+ PROCESS_STATE_HEAVY_WEIGHT = 1000;
+ // Process is in the background running a service. Unlike oom_adj, this
+ // level is used for both the normal running in background state and the
+ // executing operations state.
+ PROCESS_STATE_SERVICE = 1100;
+ // Process is in the background running a receiver. Note that from the
+ // perspective of oom_adj, receivers run at a higher foreground level, but
+ // for our prioritization here that is not necessary and putting them
+ // below services means many fewer changes in some process states as they
+ // receive broadcasts.
+ PROCESS_STATE_RECEIVER = 1200;
+ // Process is in the background but hosts the home activity.
+ PROCESS_STATE_HOME = 1300;
+ // Process is in the background but hosts the last shown activity.
+ PROCESS_STATE_LAST_ACTIVITY = 1400;
+ // Process is being cached for later use and contains activities.
+ PROCESS_STATE_CACHED_ACTIVITY = 1500;
+ // Process is being cached for later use and is a client of another cached
+ // process that contains activities.
+ PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 1600;
+ // Process is being cached for later use and is empty.
+ PROCESS_STATE_CACHED_EMPTY = 1700;
+ // Process does not exist.
+ PROCESS_STATE_NONEXISTENT = 1800;
+ }
+}
diff --git a/core/proto/android/app/alarmmanager.proto b/core/proto/android/app/alarmmanager.proto
new file mode 100644
index 0000000..789e3d6
--- /dev/null
+++ b/core/proto/android/app/alarmmanager.proto
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/app/pendingintent.proto";
+
+option java_multiple_files = true;
+
+package android.app;
+
+/**
+ * An android.app.AlarmManager object.
+ */
+message AlarmManagerProto {
+ enum AlarmType {
+ // Alarm time in System.currentTimeMillis() (wall clock time in UTC), which
+ // will wake up the device when it goes off.
+ RTC_WAKEUP = 0;
+ // Alarm time in System.currentTimeMillis() (wall clock time in UTC). This
+ // alarm does not wake the device up; if it goes off while the device is
+ // asleep, it will not be delivered until the next time the device wakes up.
+ RTC = 1;
+ // Alarm time in SystemClock.elapsedRealtime() (time since boot, including
+ // sleep), which will wake up the device when it goes off.
+ ELAPSED_REALTIME_WAKEUP = 2;
+ // Alarm time in SystemClock.elapsedRealtime() (time since boot, including
+ // sleep). This alarm does not wake the device up; if it goes off while the
+ // device is asleep, it will not be delivered until the next time the device
+ // wakes up.
+ ELAPSED_REALTIME = 3;
+ }
+}
+
+// An android.app.AlarmManager.AlarmClockInfo object.
+message AlarmClockInfoProto {
+ // This value is UTC wall clock time in milliseconds, as returned by
+ // System#currentTimeMillis() for example.
+ optional int64 trigger_time_ms = 1;
+ optional android.app.PendingIntentProto show_intent = 2;
+}
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/proto/android/app/pendingintent.proto
similarity index 70%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/proto/android/app/pendingintent.proto
index a987a16..b562c0b 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/proto/android/app/pendingintent.proto
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,15 @@
* limitations under the License.
*/
-package com.android.internal.app;
+syntax = "proto2";
-import android.graphics.Bitmap;
+option java_multiple_files = true;
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
+package android.app;
+
+/**
+ * An android.app.PendingIntent object.
+ */
+message PendingIntentProto {
+ optional string target = 1;
}
diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto
index 4f49744..3e5265a 100644
--- a/core/proto/android/content/intent.proto
+++ b/core/proto/android/content/intent.proto
@@ -15,15 +15,37 @@
*/
syntax = "proto2";
+package android.content;
+
option java_package = "android.content";
option java_multiple_files = true;
import "frameworks/base/core/proto/android/os/patternmatcher.proto";
-package android.content;
-
// Next Tag: 13
message IntentProto {
+ enum DockState {
+ // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that
+ // the phone is not in any dock.
+ DOCK_STATE_UNDOCKED = 0;
+
+ // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that
+ // the phone is in a desk dock.
+ DOCK_STATE_DESK = 1;
+
+ // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that
+ // the phone is in a car dock.
+ DOCK_STATE_CAR = 2;
+
+ // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that
+ // the phone is in a analog (low end) dock.
+ DOCK_STATE_LE_DESK = 3;
+
+ // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that
+ // the phone is in a digital (high end) dock.
+ DOCK_STATE_HE_DESK = 4;
+ }
+
optional string action = 1;
repeated string categories = 2;
optional string data = 3;
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/proto/android/internal/locallog.proto
similarity index 71%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/proto/android/internal/locallog.proto
index a987a16..51f6c1c 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/proto/android/internal/locallog.proto
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.internal.app;
+syntax = "proto2";
+package com.android.internal.util;
-import android.graphics.Bitmap;
+option java_multiple_files = true;
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
+message LocalLogProto {
+ repeated string lines = 1;
}
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/proto/android/os/batterymanager.proto
similarity index 65%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/proto/android/os/batterymanager.proto
index a987a16..669bf2d 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/proto/android/os/batterymanager.proto
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,16 @@
* limitations under the License.
*/
-package com.android.internal.app;
+syntax = "proto2";
+package android.os;
-import android.graphics.Bitmap;
+option java_multiple_files = true;
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
+message BatteryManagerProto {
+ enum PlugType {
+ PLUG_TYPE_NONE = 0;
+ PLUG_TYPE_AC = 1;
+ PLUG_TYPE_USB = 2;
+ PLUG_TYPE_WIRELESS = 4;
+ }
}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 5a5454e..e998b09 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -20,23 +20,25 @@
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/libs/incident/proto/android/section.proto";
-import "frameworks/base/core/proto/android/service/appwidget.proto";
-import "frameworks/base/core/proto/android/service/battery.proto";
-import "frameworks/base/core/proto/android/service/batterystats.proto";
-import "frameworks/base/core/proto/android/service/fingerprint.proto";
-import "frameworks/base/core/proto/android/service/diskstats.proto";
-import "frameworks/base/core/proto/android/service/netstats.proto";
-import "frameworks/base/core/proto/android/service/notification.proto";
-import "frameworks/base/core/proto/android/service/package.proto";
-import "frameworks/base/core/proto/android/service/power.proto";
-import "frameworks/base/core/proto/android/service/print.proto";
-import "frameworks/base/core/proto/android/service/procstats.proto";
-import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/os/incidentheader.proto";
import "frameworks/base/core/proto/android/os/kernelwake.proto";
import "frameworks/base/core/proto/android/os/pagetypeinfo.proto";
import "frameworks/base/core/proto/android/os/procrank.proto";
+import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
+import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto";
+import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
+import "frameworks/base/core/proto/android/server/fingerprint.proto";
+import "frameworks/base/core/proto/android/server/powermanagerservice.proto";
+import "frameworks/base/core/proto/android/service/appwidget.proto";
+import "frameworks/base/core/proto/android/service/battery.proto";
+import "frameworks/base/core/proto/android/service/batterystats.proto";
+import "frameworks/base/core/proto/android/service/diskstats.proto";
+import "frameworks/base/core/proto/android/service/netstats.proto";
+import "frameworks/base/core/proto/android/service/notification.proto";
+import "frameworks/base/core/proto/android/service/package.proto";
+import "frameworks/base/core/proto/android/service/print.proto";
+import "frameworks/base/core/proto/android/service/procstats.proto";
package android.os;
@@ -68,7 +70,7 @@
// System Services
- optional android.service.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
+ optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
(section).type = SECTION_DUMPSYS,
(section).args = "fingerprint --proto --incident"
];
@@ -105,7 +107,11 @@
(section).args = "package --proto"
];
- optional android.service.power.PowerServiceDumpProto power = 3009;
+ optional com.android.server.power.PowerManagerServiceDumpProto power = 3009 [
+ (section).type = SECTION_DUMPSYS,
+ (section).args = "power --proto"
+ ];
+
optional android.service.print.PrintServiceDumpProto print = 3010;
optional android.service.procstats.ProcessStatsServiceDumpProto procstats = 3011 [
@@ -125,4 +131,14 @@
optional com.android.server.am.proto.ServiceProto amservices = 3014;
optional com.android.server.am.proto.ProcessProto amprocesses = 3015;
+
+ optional com.android.server.AlarmManagerServiceProto alarm = 3016 [
+ (section).type = SECTION_DUMPSYS,
+ (section).args = "alarm --proto"
+ ];
+
+ optional com.android.server.wm.proto.WindowManagerServiceProto window = 3017 [
+ (section).type = SECTION_DUMPSYS,
+ (section).args = "window --proto"
+ ];
}
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index 12649e1..d032a45 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
syntax = "proto2";
+
option java_multiple_files = true;
option java_outer_classname = "WakeupSourcesProto";
+import "frameworks/base/tools/streaming_proto/stream.proto";
+
package android.os;
message KernelWakeSources {
@@ -27,6 +29,8 @@
// Next Tag: 11
message WakeupSourceProto {
+ option (stream_proto.stream).enable_fields_mapping = true;
+
// Name of the event which triggers application processor
optional string name = 1;
diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto
index f82ea76..22b3d73 100644
--- a/core/proto/android/os/pagetypeinfo.proto
+++ b/core/proto/android/os/pagetypeinfo.proto
@@ -18,6 +18,8 @@
option java_multiple_files = true;
option java_outer_classname = "PageTypeInfoProto";
+import "frameworks/base/tools/streaming_proto/stream.proto";
+
package android.os;
/*
@@ -61,6 +63,7 @@
// Next tag: 9
message BlockProto {
+ option (stream_proto.stream).enable_fields_mapping = true;
optional int32 node = 1;
diff --git a/core/proto/android/os/powermanager.proto b/core/proto/android/os/powermanager.proto
index 3bfe5d6..e9f409d 100644
--- a/core/proto/android/os/powermanager.proto
+++ b/core/proto/android/os/powermanager.proto
@@ -15,10 +15,10 @@
*/
syntax = "proto2";
-option java_multiple_files = true;
-
package android.os;
+option java_multiple_files = true;
+
message PowerManagerProto {
/* User activity events in PowerManager.java. */
enum UserActivityEvent {
@@ -31,4 +31,84 @@
// Accessibility taking action on behalf of user.
USER_ACTIVITY_EVENT_ACCESSIBILITY = 3;
}
+
+ enum WakeLockLevel {
+ // NOTE: Wake lock levels were previously defined as a bit field, except
+ // that only a few combinations were actually supported so the bit field
+ // was removed. This explains why the numbering scheme is so odd. If
+ // adding a new wake lock level, any unused value can be used.
+
+ // Ensures that the CPU is running; the screen and keyboard backlight
+ // will be allowed to go off.
+ PARTIAL_WAKE_LOCK = 1;
+
+ // Ensures that the screen is on (but may be dimmed); the keyboard
+ // backlight will be allowed to go off. If the user presses the power
+ // button, then the SCREEN_DIM_WAKE_LOCK will be implicitly released by
+ // the system, causing both the screen and the CPU to be turned off.
+ SCREEN_DIM_WAKE_LOCK = 6 [deprecated = true];
+
+ // Ensures that the screen is on at full brightness; the keyboard
+ // backlight will be allowed to go off. If the user presses the power
+ // button, then the SCREEN_BRIGHT_WAKE_LOCK will be implicitly released
+ // by the system, causing both the screen and the CPU to be turned off.
+ SCREEN_BRIGHT_WAKE_LOCK = 10 [deprecated = true];
+
+ // Ensures that the screen and keyboard backlight are on at full
+ // brightness. If the user presses the power button, then the
+ // FULL_WAKE_LOCK will be implicitly released by the system, causing
+ // both the screen and the CPU to be turned off.
+ FULL_WAKE_LOCK = 26 [deprecated = true];
+
+ // Turns the screen off when the proximity sensor activates. If the
+ // proximity sensor detects that an object is nearby, the screen turns
+ // off immediately. Shortly after the object moves away, the screen
+ // turns on again.
+ // A proximity wake lock does not prevent the device from falling asleep
+ // unlike FULL_WAKE_LOCK, SCREEN_BRIGHT_WAKE_LOCK and
+ // SCREEN_DIM_WAKE_LOCK. If there is no user activity and no other wake
+ // locks are held, then the device will fall asleep (and lock) as usual.
+ // However, the device will not fall asleep while the screen has been
+ // turned off by the proximity sensor because it effectively counts as
+ // ongoing user activity.
+ PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32;
+
+ // Put the screen in a low power state and allow the CPU to suspend if
+ // no other wake locks are held. This is used by the dream manager to
+ // implement doze mode. It currently has no effect unless the power
+ // manager is in the dozing state.
+ DOZE_WAKE_LOCK = 64;
+
+ // Keep the device awake enough to allow drawing to occur. This is used
+ // by the window manager to allow applications to draw while the system
+ // is dozing. It currently has no effect unless the power manager is in
+ // the dozing state.
+ DRAW_WAKE_LOCK = 128;
+ }
+}
+
+message PowerManagerInternalProto {
+ // Enum values gotten from PowerManagerInternal.java
+ enum Wakefulness {
+ // The device is asleep. It can only be awoken by a call to wakeUp().
+ // The screen should be off or in the process of being turned off by the
+ // display controller. The device typically passes through the dozing
+ // state first.
+ WAKEFULNESS_ASLEEP = 0;
+ // The device is fully awake. It can be put to sleep by a call to
+ // goToSleep(). When the user activity timeout expires, the device may
+ // start dreaming or go to sleep.
+ WAKEFULNESS_AWAKE = 1;
+ // The device is dreaming. It can be awoken by a call to wakeUp(), which
+ // ends the dream. The device goes to sleep when goToSleep() is called,
+ // when the dream ends, or when unplugged. User activity may brighten
+ // the screen but does not end the dream.
+ WAKEFULNESS_DREAMING = 2;
+ // The device is dozing. It is almost asleep but is allowing a special
+ // low-power "doze" dream to run which keeps the display on but lets the
+ // application processor suspend. It can be awoken by a call to wakeUp()
+ // which ends the dream. The device fully goes to sleep if the dream
+ // cannot be started or ends on its own.
+ WAKEFULNESS_DOZING = 3;
+ }
}
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
index ab6a6a3..4d62a60 100644
--- a/core/proto/android/os/procrank.proto
+++ b/core/proto/android/os/procrank.proto
@@ -18,6 +18,8 @@
option java_multiple_files = true;
option java_outer_classname = "ProcrankProto";
+import "frameworks/base/tools/streaming_proto/stream.proto";
+
package android.os;
//Memory usage of running processes
@@ -31,6 +33,8 @@
// Next Tag: 11
message ProcessProto {
+ option (stream_proto.stream).enable_fields_mapping = true;
+
// ID of the process
optional int32 pid = 1;
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index f092713..3411c6a 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -599,6 +599,14 @@
optional bool default_from_system = 6;
}
+message SettingsProto {
+ // Enum values gotten from Settings.java
+ enum ScreenBrightnessMode {
+ SCREEN_BRIGHTNESS_MODE_MANUAL = 0;
+ SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1;
+ }
+}
+
message SettingsOperationProto {
// When the operation happened
optional int64 timestamp = 1;
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 788ac8f..c57cb72 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -69,12 +69,11 @@
optional string real_activity = 6;
optional string orig_activity = 7;
optional int32 activity_type = 8;
- optional int32 return_to_type = 9;
- optional int32 resize_mode = 10;
- optional bool fullscreen = 11;
- optional .android.graphics.RectProto bounds = 12;
- optional int32 min_width = 13;
- optional int32 min_height = 14;
+ optional int32 resize_mode = 9;
+ optional bool fullscreen = 10;
+ optional .android.graphics.RectProto bounds = 11;
+ optional int32 min_width = 12;
+ optional int32 min_height = 13;
}
message ActivityRecordProto {
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto
new file mode 100644
index 0000000..d2cd190
--- /dev/null
+++ b/core/proto/android/server/alarmmanagerservice.proto
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/app/alarmmanager.proto";
+import "frameworks/base/core/proto/android/app/pendingintent.proto";
+import "frameworks/base/core/proto/android/internal/locallog.proto";
+import "frameworks/base/core/proto/android/os/worksource.proto";
+
+package com.android.server;
+
+option java_multiple_files = true;
+
+message AlarmManagerServiceProto {
+ optional int64 current_time = 1;
+ optional int64 elapsed_realtime = 2;
+ optional int64 last_time_change_clock_time = 3;
+ optional int64 last_time_change_realtime = 4;
+ // Current settings
+ optional ConstantsProto settings = 5;
+ // UIDs currently in the foreground.
+ repeated int32 foreground_uids = 6;
+ // Packages forced into app standby.
+ repeated string forced_app_standby_packages = 7;
+
+ optional bool is_interactive = 8;
+ // Only valid if is_interactive is false.
+ optional int64 time_since_non_interactive_ms = 9;
+ // Only valid if is_interactive is false.
+ optional int64 max_wakeup_delay_ms = 10;
+ // Only valid if is_interactive is false.
+ optional int64 time_since_last_dispatch_ms = 11;
+ // Only valid if is_interactive is false.
+ optional int64 time_until_next_non_wakeup_delivery_ms = 12;
+
+ optional int64 time_until_next_non_wakeup_alarm_ms = 13;
+ optional int64 time_until_next_wakeup_ms = 14;
+ optional int64 time_since_last_wakeup_ms = 15;
+ // Time since the last wakeup was set.
+ optional int64 time_since_last_wakeup_set_ms = 16;
+ optional int64 time_change_event_count = 17;
+ // The current set of user whitelisted apps for device idle mode, meaning
+ // these are allowed to freely schedule alarms. These are app IDs, not UIDs.
+ repeated int32 device_idle_user_whitelist_app_ids = 18;
+
+ repeated AlarmClockMetadataProto next_alarm_clock_metadata = 19;
+
+ repeated BatchProto pending_alarm_batches = 20;
+
+ // List of alarms per uid deferred due to user applied background restrictions
+ // on the source app.
+ repeated AlarmProto pending_user_blocked_background_alarms = 21;
+
+ // When idling mode will end. Will be empty if the device is not currently
+ // idling.
+ optional AlarmProto pending_idle_until = 22;
+
+ // Any alarms that we don't want to run during idle mode. Will be empty if the
+ // device is not currently idling.
+ repeated AlarmProto pending_while_idle_alarms = 23;
+
+ // This is a special alarm that will put the system into idle until it goes
+ // off. The caller has given the time they want this to happen at.
+ optional AlarmProto next_wake_from_idle = 24;
+
+ repeated AlarmProto past_due_non_wakeup_alarms = 25;
+
+ // Number of delayed alarms.
+ optional int32 delayed_alarm_count = 26;
+ // The total amount of time alarms had been delayed. Overlapping alarms are
+ // only counted once (ie. If two alarms were meant to trigger at the same time
+ // but were delayed by 5 seconds, the total time would be 5 seconds).
+ optional int64 total_delay_time_ms = 27;
+ optional int64 max_delay_duration_ms = 28;
+ optional int64 max_non_interactive_duration_ms = 29;
+
+ optional int32 broadcast_ref_count = 30;
+ // Canonical count of (operation.send() - onSendFinished()) and listener
+ // send/complete/timeout invocations.
+ optional int32 pending_intent_send_count = 31;
+ optional int32 pending_intent_finish_count = 32;
+ optional int32 listener_send_count = 33;
+ optional int32 listener_finish_count = 34;
+
+ repeated InFlightProto outstanding_deliveries = 35;
+
+ // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling. It
+ // should be either CosntantsProto.allow_while_idle_short_duration_ms or
+ // ConstantsProto.allow_while_idle_long_duration_ms.
+ optional int64 allow_while_idle_min_duration_ms = 36;
+
+ message LastAllowWhileIdleDispatch {
+ optional int32 uid = 1;
+ // In the 'elapsed' timebase.
+ optional int64 time_ms = 2;
+ }
+ // For each uid, this is the last time we dispatched an "allow while idle"
+ // alarm, used to determine the earliest we can dispatch the next such alarm.
+ repeated LastAllowWhileIdleDispatch last_allow_while_idle_dispatch_times = 37;
+
+ optional com.android.internal.util.LocalLogProto recent_problems = 38;
+
+ message TopAlarm {
+ optional int32 uid = 1;
+ optional string package_name = 2;
+ optional FilterStatsProto filter = 3;
+ }
+ repeated TopAlarm top_alarms = 39;
+
+ message AlarmStat {
+ optional BroadcastStatsProto broadcast = 1;
+ repeated FilterStatsProto filters = 2;
+ }
+ repeated AlarmStat alarm_stats = 40;
+
+ repeated IdleDispatchEntryProto allow_while_idle_dispatches = 41;
+ repeated WakeupEventProto recent_wakeup_history = 42;
+}
+
+// This is a soft wrapper for alarm clock information. It is not representative
+// of an android.app.AlarmManager.AlarmClockInfo object.
+message AlarmClockMetadataProto {
+ optional int32 user = 1;
+ optional bool is_pending_send = 2;
+ // This value is UTC wall clock time in milliseconds, as returned by
+ // System#currentTimeMillis() for example.
+ optional int64 trigger_time_ms = 3;
+}
+
+// A com.android.server.AlarmManagerService.Alarm object.
+message AlarmProto {
+ optional string tag = 1;
+ optional .android.app.AlarmManagerProto.AlarmType type = 2;
+ // How long until the alarm goes off, in the 'elapsed' timebase.
+ optional int64 when_elapsed_ms = 3;
+ optional int64 window_length_ms = 4;
+ optional int64 repeat_interval_ms = 5;
+ optional int32 count = 6;
+ optional int32 flags = 7;
+ optional .android.app.AlarmClockInfoProto alarm_clock = 8;
+ optional .android.app.PendingIntentProto operation = 9;
+ optional string listener = 10;
+}
+
+// A com.android.server.AlarmManagerService.Batch object.
+message BatchProto {
+ // Start time in terms of elapsed realtime.
+ optional int64 start_realtime = 1;
+ // End time in terms of elapsed realtime.
+ optional int64 end_realtime = 2;
+ optional int32 flags = 3;
+ repeated AlarmProto alarms = 4;
+}
+
+// A com.android.server.AlarmManagerService.BroadcastStats object.
+message BroadcastStatsProto {
+ optional int32 uid = 1;
+ optional string package_name = 2;
+ // The total amount of time this broadcast was in flight.
+ optional int64 total_flight_duration_ms = 3;
+ optional int32 count = 4;
+ optional int32 wakeup_count = 5;
+ // The last time this first became active (when nesting changed from 0 to 1)
+ // in terms of elapsed realtime.
+ optional int64 start_time_realtime = 6;
+ // The broadcast is active if nesting > 0.
+ optional int32 nesting = 7;
+}
+
+// A com.android.server.AlarmManagerService.Constants object.
+message ConstantsProto {
+ // Minimum futurity of a new alarm.
+ optional int64 min_futurity_duration_ms = 1;
+ // Minimum alarm recurrence interval.
+ optional int64 min_interval_duration_ms = 2;
+ // Direct alarm listener callback timeout.
+ optional int64 listener_timeout_duration_ms = 3;
+ // Minimum time between ALLOW_WHILE_IDLE alarms when system is not idle.
+ optional int64 allow_while_idle_short_duration_ms = 4;
+ // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling.
+ optional int64 allow_while_idle_long_duration_ms = 5;
+ // BroadcastOptions.setTemporaryAppWhitelistDuration() to use for FLAG_ALLOW_WHILE_IDLE.
+ optional int64 allow_while_idle_whitelist_duration_ms = 6;
+}
+
+// A com.android.server.AlarmManagerService.FilterStats object.
+message FilterStatsProto {
+ optional string tag = 1;
+ // The last time this filter when in flight, in terms of elapsed realtime.
+ optional int64 last_flight_time_realtime = 2;
+ // The total amount of time this filter was in flight.
+ optional int64 total_flight_duration_ms = 3;
+ optional int32 count = 4;
+ optional int32 wakeup_count = 5;
+ // The last time this first became active (when nesting changed from 0 to 1)
+ // in terms of elapsed realtime.
+ optional int64 start_time_realtime = 6;
+ // The filter is active if nesting > 0.
+ optional int32 nesting = 7;
+}
+
+// A com.android.server.AlarmManagerService.IdleDispatchEntry object.
+message IdleDispatchEntryProto {
+ optional int32 uid = 1;
+ optional string pkg = 2;
+ optional string tag = 3;
+ optional string op = 4;
+ // Time when this entry was created, in terms of elapsed realtime.
+ optional int64 entry_creation_realtime = 5;
+ // For a RESCHEDULED op, this is the last time we dispatched an "allow while
+ // idle" alarm for the UID. For a SET op, this is when the alarm was
+ // triggered. Times are in the 'elapsed' timebase.
+ optional int64 arg_realtime = 6;
+}
+
+// A com.android.server.AlarmManagerService.InFlight object.
+message InFlightProto {
+ optional int32 uid = 1;
+ optional string tag = 2;
+ optional int64 when_elapsed_ms = 3;
+ optional .android.app.AlarmManagerProto.AlarmType alarm_type = 4;
+ optional .android.app.PendingIntentProto pending_intent = 5;
+ optional BroadcastStatsProto broadcast_stats = 6;
+ optional FilterStatsProto filter_stats = 7;
+ optional .android.os.WorkSourceProto work_source = 8;
+}
+
+// A com.android.server.AlarmManagerService.WakeupEvent object.
+message WakeupEventProto {
+ optional int32 uid = 1;
+ optional string action = 2;
+ optional int64 when = 3;
+}
diff --git a/core/proto/android/service/fingerprint.proto b/core/proto/android/server/fingerprint.proto
similarity index 85%
rename from core/proto/android/service/fingerprint.proto
rename to core/proto/android/server/fingerprint.proto
index 0826ad5..ec4ffe0 100644
--- a/core/proto/android/service/fingerprint.proto
+++ b/core/proto/android/server/fingerprint.proto
@@ -15,7 +15,7 @@
*/
syntax = "proto2";
-package android.service.fingerprint;
+package com.android.server.fingerprint;
option java_multiple_files = true;
option java_outer_classname = "FingerprintServiceProto";
@@ -33,14 +33,15 @@
optional int32 num_fingerprints = 2;
// Normal fingerprint authentications (e.g. lockscreen).
- optional FingerprintActionStatsProto normal = 3;
+ optional PerformanceStatsProto normal = 3;
// Crypto authentications (e.g. to unlock password storage, make secure
// purchases, etc).
- optional FingerprintActionStatsProto crypto = 4;
+ optional PerformanceStatsProto crypto = 4;
}
-message FingerprintActionStatsProto {
+// A com.android.server.fingerprint.FingerpintService.PerformanceStats object.
+message PerformanceStatsProto {
// Number of accepted fingerprints.
optional int32 accept = 1;
@@ -55,5 +56,5 @@
optional int32 lockout = 4;
// Total number of permanent lockouts.
- optional int32 lockout_permanent = 5;
+ optional int32 permanent_lockout = 5;
}
diff --git a/core/proto/android/service/power.proto b/core/proto/android/server/powermanagerservice.proto
similarity index 73%
rename from core/proto/android/service/power.proto
rename to core/proto/android/server/powermanagerservice.proto
index 5d53847..d442acf 100644
--- a/core/proto/android/service/power.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -15,16 +15,22 @@
*/
syntax = "proto2";
-package android.service.power;
+package com.android.server.power;
option java_multiple_files = true;
-option java_outer_classname = "PowerServiceProto";
+import "frameworks/base/core/proto/android/app/activitymanager.proto";
+import "frameworks/base/core/proto/android/content/intent.proto";
+import "frameworks/base/core/proto/android/os/batterymanager.proto";
import "frameworks/base/core/proto/android/os/looper.proto";
+import "frameworks/base/core/proto/android/os/powermanager.proto";
import "frameworks/base/core/proto/android/os/worksource.proto";
-import "frameworks/base/core/proto/android/service/wirelesschargerdetector.proto";
+import "frameworks/base/core/proto/android/providers/settings.proto";
+import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto";
+import "frameworks/base/core/proto/android/view/display.proto";
-message PowerServiceDumpProto {
+message PowerManagerServiceDumpProto {
+ // A com.android.server.power.PowerManagerService.Constants object.
message ConstantsProto {
optional bool is_no_cached_wake_locks = 1;
}
@@ -44,79 +50,14 @@
optional bool is_screen_dim = 2;
optional bool is_screen_dream = 3;
}
- message UidProto {
- // Enum values gotten from ActivityManager.java
- enum ProcessState {
- // Process is a persistent system process.
- PROCESS_STATE_PERSISTENT = 0;
- // Process is a persistent system process and is doing UI.
- PROCESS_STATE_PERSISTENT_UI = 1;
- // Process is hosting the current top activities. Note that this
- // covers all activities that are visible to the user.
- PROCESS_STATE_TOP = 2;
- // Process is hosting a foreground service due to a system binding.
- PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 3;
- // Process is hosting a foreground service.
- PROCESS_STATE_FOREGROUND_SERVICE = 4;
- // Same as {@link #PROCESS_STATE_TOP} but while device is sleeping.
- PROCESS_STATE_TOP_SLEEPING = 5;
- // Process is important to the user, and something they are aware of.
- PROCESS_STATE_IMPORTANT_FOREGROUND = 6;
- // Process is important to the user, but not something they are aware of.
- PROCESS_STATE_IMPORTANT_BACKGROUND = 7;
- // Process is in the background running a backup/restore operation.
- PROCESS_STATE_BACKUP = 8;
- // Process is in the background, but it can't restore its state so
- // we want to try to avoid killing it.
- PROCESS_STATE_HEAVY_WEIGHT = 9;
- // Process is in the background running a service.
- PROCESS_STATE_SERVICE = 10;
- // Process is in the background running a receiver.
- PROCESS_STATE_RECEIVER = 11;
- // Process is in the background but hosts the home activity.
- PROCESS_STATE_HOME = 12;
- // Process is in the background but hosts the last shown activity.
- PROCESS_STATE_LAST_ACTIVITY = 13;
- // Process is being cached for later use and contains activities.
- PROCESS_STATE_CACHED_ACTIVITY = 14;
- // Process is being cached for later use and is a client of another
- // cached process that contains activities.
- PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 15;
- // Process is being cached for later use and is empty.
- PROCESS_STATE_CACHED_EMPTY = 16;
- // Process does not exist.
- PROCESS_STATE_NONEXISTENT = 17;
- }
+ // A com.android.server.power.PowerManagerService.UidState object.
+ message UidStateProto {
optional int32 uid = 1;
optional string uid_string = 2;
optional bool is_active = 3;
optional int32 num_wake_locks = 4;
optional bool is_process_state_unknown = 5;
- optional ProcessState process_state = 6;
- }
-
- // Enum values gotten from PowerManagerInternal.java
- enum Wakefulness {
- WAKEFULNESS_ASLEEP = 0;
- WAKEFULNESS_AWAKE = 1;
- WAKEFULNESS_DREAMING = 2;
- WAKEFULNESS_DOZING = 3;
- WAKEFULNESS_UNKNOWN = 4;
- }
- // Enum values gotten from BatteryManager.java
- enum PlugType {
- PLUG_TYPE_NONE = 0;
- PLUG_TYPE_PLUGGED_AC = 1;
- PLUG_TYPE_PLUGGED_USB = 2;
- PLUG_TYPE_PLUGGED_WIRELESS = 4;
- }
- // Enum values gotten from Intent.java
- enum DockState {
- DOCK_STATE_UNDOCKED = 0;
- DOCK_STATE_DESK = 1;
- DOCK_STATE_CAR = 2;
- DOCK_STATE_LE_DESK = 3;
- DOCK_STATE_HE_DESK = 4;
+ optional .android.app.ActivityManagerProto.ProcessState process_state = 6;
}
optional ConstantsProto constants = 1;
@@ -124,18 +65,18 @@
// changed and need to be recalculated.
optional int32 dirty = 2;
// Indicates whether the device is awake or asleep or somewhere in between.
- optional Wakefulness wakefulness = 3;
+ optional .android.os.PowerManagerInternalProto.Wakefulness wakefulness = 3;
optional bool is_wakefulness_changing = 4;
// True if the device is plugged into a power source.
optional bool is_powered = 5;
// The current plug type
- optional PlugType plug_type = 6;
+ optional .android.os.BatteryManagerProto.PlugType plug_type = 6;
// The current battery level percentage.
optional int32 battery_level = 7;
// The battery level percentage at the time the dream started.
optional int32 battery_level_when_dream_started = 8;
// The current dock state.
- optional DockState dock_state = 9;
+ optional .android.content.IntentProto.DockState dock_state = 9;
// True if the device should stay on.
optional bool is_stay_on = 10;
// True if the proximity sensor reads a positive result.
@@ -215,8 +156,8 @@
// Some uids have actually changed while mUidsChanging was true.
optional bool are_uids_changed = 45;
// List of UIDs and their states
- repeated UidProto uids = 46;
- optional android.os.LooperProto looper = 47;
+ repeated UidStateProto uid_states = 46;
+ optional .android.os.LooperProto looper = 47;
// List of all wake locks acquired by applications.
repeated WakeLockProto wake_locks = 48;
// List of all suspend blockers.
@@ -224,11 +165,13 @@
optional WirelessChargerDetectorProto wireless_charger_detector = 50;
}
+// A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
message SuspendBlockerProto {
optional string name = 1;
optional int32 reference_count = 2;
}
+// A com.android.server.power.PowerManagerService.WakeLock object.
message WakeLockProto {
message WakeLockFlagsProto {
// Turn the screen on when the wake lock is acquired.
@@ -238,27 +181,7 @@
optional bool is_on_after_release = 2;
}
- // Enum values gotten from PowerManager.java
- enum LockLevel {
- WAKE_LOCK_INVALID = 0;
- // Ensures that the CPU is running.
- PARTIAL_WAKE_LOCK = 1;
- // Ensures that the screen is on (but may be dimmed).
- SCREEN_DIM_WAKE_LOCK = 6;
- // Ensures that the screen is on at full brightness.
- SCREEN_BRIGHT_WAKE_LOCK = 10;
- // Ensures that the screen and keyboard backlight are on at full brightness.
- FULL_WAKE_LOCK = 26;
- // Turns the screen off when the proximity sensor activates.
- PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32;
- // Put the screen in a low power state and allow the CPU to suspend
- // if no other wake locks are held.
- DOZE_WAKE_LOCK = 64;
- // Keep the device awake enough to allow drawing to occur.
- DRAW_WAKE_LOCK = 128;
- }
-
- optional LockLevel lock_level = 1;
+ optional .android.os.PowerManagerProto.WakeLockLevel lock_level = 1;
optional string tag = 2;
optional WakeLockFlagsProto flags = 3;
optional bool is_disabled = 4;
@@ -269,7 +192,7 @@
optional int32 uid = 7;
// Owner PID
optional int32 pid = 8;
- optional android.os.WorkSourceProto work_source = 9;
+ optional .android.os.WorkSourceProto work_source = 9;
}
message PowerServiceSettingsAndConfigurationDumpProto {
@@ -285,22 +208,6 @@
optional int32 setting_for_vr_default = 4;
}
- // Enum values gotten from Settings.java
- enum ScreenBrightnessMode {
- SCREEN_BRIGHTNESS_MODE_MANUAL = 0;
- SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1;
- }
- // Enum values gotten from Display.java
- enum DisplayState {
- DISPLAY_STATE_UNKNOWN = 0;
- DISPLAY_STATE_OFF = 1;
- DISPLAY_STATE_ON = 2;
- DISPLAY_STATE_DOZE = 3;
- DISPLAY_STATE_DOZE_SUSPEND = 4;
- DISPLAY_STATE_VR = 5;
- }
-
-
// True to decouple auto-suspend mode from the display state.
optional bool is_decouple_hal_auto_suspend_mode_from_display_config = 1;
// True to decouple interactive mode from the display state.
@@ -371,7 +278,7 @@
// Use 0 if there is no adjustment.
optional float screen_auto_brightness_adjustment_setting = 31;
// The screen brightness mode.
- optional ScreenBrightnessMode screen_brightness_mode_setting = 32;
+ optional .android.providers.settings.SettingsProto.ScreenBrightnessMode screen_brightness_mode_setting = 32;
// The screen brightness setting override from the window manager
// to allow the current foreground activity to override the brightness.
// Use -1 to disable.
@@ -393,7 +300,7 @@
// Use NaN to disable.
optional float temporary_screen_auto_brightness_adjustment_setting_override = 37;
// The screen state to use while dozing.
- optional DisplayState doze_screen_state_override_from_dream_manager = 38;
+ optional .android.view.DisplayProto.DisplayState doze_screen_state_override_from_dream_manager = 38;
// The screen brightness to use while dozing.
optional float dozed_screen_brightness_override_from_dream_manager = 39;
// Screen brightness settings limits.
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 064523a..4d48a42 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -15,6 +15,7 @@
*/
syntax = "proto2";
+
import "frameworks/base/core/proto/android/content/configuration.proto";
import "frameworks/base/core/proto/android/graphics/rect.proto";
import "frameworks/base/core/proto/android/view/displayinfo.proto";
diff --git a/core/proto/android/server/windowmanagertrace.proto b/core/proto/android/server/windowmanagertrace.proto
new file mode 100644
index 0000000..0c65bb2
--- /dev/null
+++ b/core/proto/android/server/windowmanagertrace.proto
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/content/configuration.proto";
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
+import "frameworks/base/core/proto/android/view/displayinfo.proto";
+import "frameworks/base/core/proto/android/view/windowlayoutparams.proto";
+
+package com.android.server.wm.proto;
+
+option java_multiple_files = true;
+
+/* represents a file full of window manager trace entries.
+ Encoded, it should start with 0x9 0x57 0x49 0x4e 0x54 0x52 0x41 0x43 0x45 (.WINTRACE), such
+ that they can be easily identified. */
+message WindowManagerTraceFileProto {
+
+ /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+ (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+ constants into .proto files. */
+ enum MagicNumber {
+ INVALID = 0;
+ MAGIC_NUMBER_L = 0x544e4957; /* WINT (little-endian ASCII) */
+ MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
+ }
+
+ optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
+ repeated WindowManagerTraceProto entry = 2;
+}
+
+/* one window manager trace entry. */
+message WindowManagerTraceProto {
+ /* required: elapsed realtime in nanos since boot of when this entry was logged */
+ optional fixed64 elapsed_realtime_nanos = 1;
+
+ /* where the trace originated */
+ optional string where = 2;
+
+ optional WindowManagerServiceProto window_manager_service = 3;
+}
diff --git a/core/proto/android/service/wirelesschargerdetector.proto b/core/proto/android/server/wirelesschargerdetector.proto
similarity index 97%
rename from core/proto/android/service/wirelesschargerdetector.proto
rename to core/proto/android/server/wirelesschargerdetector.proto
index bd697c8..89cf2f8 100644
--- a/core/proto/android/service/wirelesschargerdetector.proto
+++ b/core/proto/android/server/wirelesschargerdetector.proto
@@ -15,7 +15,7 @@
*/
syntax = "proto2";
-package android.service.power;
+package com.android.server.power;
option java_multiple_files = true;
@@ -46,4 +46,4 @@
optional VectorProto first_sample = 9;
// The value of the last sample that was collected.
optional VectorProto last_sample = 10;
-}
\ No newline at end of file
+}
diff --git a/core/proto/android/service/battery.proto b/core/proto/android/service/battery.proto
index 998a808..8382b82 100644
--- a/core/proto/android/service/battery.proto
+++ b/core/proto/android/service/battery.proto
@@ -20,13 +20,9 @@
option java_multiple_files = true;
option java_outer_classname = "BatteryServiceProto";
+import "frameworks/base/core/proto/android/os/batterymanager.proto";
+
message BatteryServiceDumpProto {
- enum BatteryPlugged {
- BATTERY_PLUGGED_NONE = 0;
- BATTERY_PLUGGED_AC = 1;
- BATTERY_PLUGGED_USB = 2;
- BATTERY_PLUGGED_WIRELESS = 4;
- }
enum BatteryStatus {
BATTERY_STATUS_INVALID = 0;
BATTERY_STATUS_UNKNOWN = 1;
@@ -49,7 +45,7 @@
// If true: UPDATES STOPPED -- use 'reset' to restart
optional bool are_updates_stopped = 1;
// Plugged status of power sources
- optional BatteryPlugged plugged = 2;
+ optional android.os.BatteryManagerProto.PlugType plugged = 2;
// Max current in microamperes
optional int32 max_charging_current = 3;
// Max voltage
diff --git a/core/proto/android/view/display.proto b/core/proto/android/view/display.proto
new file mode 100644
index 0000000..210c6d1
--- /dev/null
+++ b/core/proto/android/view/display.proto
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.view;
+
+option java_multiple_files = true;
+
+message DisplayProto {
+ enum DisplayState {
+ // The display state is unknown.
+ DISPLAY_STATE_UNKNOWN = 0;
+ // The display state is off.
+ DISPLAY_STATE_OFF = 1;
+ // The display state is on.
+ DISPLAY_STATE_ON = 2;
+ // The display is dozing in a low power state; it is still on but is
+ // optimized for showing system-provided content while the device is
+ // non-interactive.
+ DISPLAY_STATE_DOZE = 3;
+ // The display is dozing in a suspended low power state; it is still on
+ // but is optimized for showing static system-provided content while the
+ // device is non-interactive.
+ DISPLAY_STATE_DOZE_SUSPEND = 4;
+ // The display is on and optimized for VR mode.
+ DISPLAY_STATE_VR = 5;
+ }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 105bb7e..4a4de24 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -183,15 +183,23 @@
<protected-broadcast
android:name="android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED" />
<protected-broadcast
+ android:name="android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
android:name="android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
+ android:name="android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST" />
+ <protected-broadcast
+ android:name="android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT" />
+ <protected-broadcast
android:name="android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
+ android:name="android.bluetooth.input.profile.action.IDLE_TIME_CHANGED" />
+ <protected-broadcast
android:name="android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED" />
<protected-broadcast
android:name="android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS" />
<protected-broadcast
- android:name="android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED" />
+ android:name="android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
android:name="android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED" />
@@ -318,6 +326,7 @@
<protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_SCAN_AVAILABLE" />
<protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" />
<protected-broadcast android:name="android.net.wifi.RSSI_CHANGED" />
<protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
@@ -3053,10 +3062,10 @@
android:protectionLevel="signature|privileged|development|appop" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
- <!-- @hide Allows an application to change the app idle state of an app.
+ <!-- @hide @SystemApi Allows an application to change the app idle state of an app.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|privileged" />
<!-- @hide @SystemApi Allows an application to temporarily whitelist an inactive app to
access the network and acquire wakelocks.
diff --git a/core/res/res/drawable/dialog_background_material.xml b/core/res/res/drawable/dialog_background_material.xml
index 2f8d1fa..e017d3c 100644
--- a/core/res/res/drawable/dialog_background_material.xml
+++ b/core/res/res/drawable/dialog_background_material.xml
@@ -17,7 +17,7 @@
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:inset="16dp">
<shape android:shape="rectangle">
- <corners android:radius="2dp" />
+ <corners android:radius="?attr/dialogCornerRadius" />
<solid android:color="?attr/colorBackground" />
</shape>
</inset>
diff --git a/core/res/res/layout/magnifier.xml b/core/res/res/layout/magnifier.xml
index d6cd8b4..f3344c7 100644
--- a/core/res/res/layout/magnifier.xml
+++ b/core/res/res/layout/magnifier.xml
@@ -22,10 +22,11 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/magnifier_inner"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="@android:dimen/magnifier_width"
+ android:layout_height="@android:dimen/magnifier_height"
+ android:elevation="@android:dimen/magnifier_elevation"
android:background="?android:attr/floatingToolbarPopupBackgroundDrawable"
- android:elevation="@android:dimen/magnifier_elevation">
+ android:scaleType="fitXY">
<ImageView
android:id="@+id/magnifier_image"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/slice_grid.xml b/core/res/res/layout/slice_grid.xml
index 15ded7b..4cb78e1 100644
--- a/core/res/res/layout/slice_grid.xml
+++ b/core/res/res/layout/slice_grid.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<android.app.slice.views.GridView
+<android.app.slice.widget.GridView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -21,4 +21,4 @@
android:gravity="center_vertical"
android:background="?android:attr/activatedBackgroundIndicator"
android:clipToPadding="false">
-</android.app.slice.views.GridView>
+</android.app.slice.widget.GridView>
diff --git a/core/res/res/layout/slice_message.xml b/core/res/res/layout/slice_message.xml
index 96f8078..9e0cfe7 100644
--- a/core/res/res/layout/slice_message.xml
+++ b/core/res/res/layout/slice_message.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<android.app.slice.views.MessageView
+<android.app.slice.widget.MessageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -48,4 +48,4 @@
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItem"
android:maxLines="10" />
-</android.app.slice.views.MessageView>
+</android.app.slice.widget.MessageView>
diff --git a/core/res/res/layout/slice_message_local.xml b/core/res/res/layout/slice_message_local.xml
index 5c767ba..d806ddd 100644
--- a/core/res/res/layout/slice_message_local.xml
+++ b/core/res/res/layout/slice_message_local.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<android.app.slice.views.MessageView
+<android.app.slice.widget.MessageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -35,4 +35,4 @@
android:background="#ffeeeeee"
android:maxLines="10" />
-</android.app.slice.views.MessageView>
+</android.app.slice.widget.MessageView>
diff --git a/core/res/res/layout/slice_remote_input.xml b/core/res/res/layout/slice_remote_input.xml
index 90d0c82..670c1e9 100644
--- a/core/res/res/layout/slice_remote_input.xml
+++ b/core/res/res/layout/slice_remote_input.xml
@@ -15,14 +15,14 @@
limitations under the License.
-->
<!-- LinearLayout -->
-<android.app.slice.views.RemoteInputView
+<android.app.slice.widget.RemoteInputView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/remote_input"
android:background="@drawable/slice_remote_input_bg"
android:layout_height="match_parent"
android:layout_width="match_parent">
- <view class="com.android.internal.slice.view.RemoteInputView$RemoteEditText"
+ <view class="com.android.internal.app.slice.widget.RemoteInputView$RemoteEditText"
android:id="@+id/remote_input_text"
android:layout_height="match_parent"
android:layout_width="0dp"
@@ -73,4 +73,4 @@
</FrameLayout>
-</android.app.slice.views.RemoteInputView>
\ No newline at end of file
+</android.app.slice.widget.RemoteInputView>
\ No newline at end of file
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index a365c15..dddd52b 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1726,7 +1726,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"العمل الثاني <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"العمل الثالث <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"لإزالة تثبيت هذه الشاشة، يمكنك لمس زرّي \"رجوع\" و\"نظرة عامة\" مع الاستمرار"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"لا يمكن إزالة تثبيت هذا التطبيق"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"تم تثبيت الشاشة"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"تم إلغاء تثبيت الشاشة"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"المطالبة برقم التعريف الشخصي قبل إزالة التثبيت"</string>
@@ -1922,14 +1921,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"اختبار رسائل الطوارئ"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"الرد"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"غير مسموح باستخدام SIM للصوت"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"لم يتم توفير SIM للصوت"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"غير مسموح باستخدام SIM للصوت"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"غير مسموح باستخدام الهاتف للصوت"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"نافذة منبثقة"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"يتطلب هذا الاختصار أحدث تطبيق"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 0b82ffb..7a90719 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -278,8 +278,8 @@
<string name="permgroupdesc_storage" msgid="637758554581589203">"pristupa slikama, medijima i datotekama na uređaju"</string>
<string name="permgrouprequest_storage" msgid="7429669910547860218">"Dozvolite <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> da pristupa slikama, medijskim datotekama i datotekama na uređaju"</string>
<string name="permgrouplab_microphone" msgid="171539900250043464">"Mikrofon"</string>
- <string name="permgroupdesc_microphone" msgid="4988812113943554584">"snima audio"</string>
- <string name="permgrouprequest_microphone" msgid="8065941268709600606">"Dozvolite da <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> snima audio snimke"</string>
+ <string name="permgroupdesc_microphone" msgid="4988812113943554584">"snima zvuk"</string>
+ <string name="permgrouprequest_microphone" msgid="8065941268709600606">"Dozvolite da <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> snima zvuk"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"Kamera"</string>
<string name="permgroupdesc_camera" msgid="3250611594678347720">"snima slike i video"</string>
<string name="permgrouprequest_camera" msgid="810824326507258410">"Dozvolite da <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> snima slike i video snimke"</string>
@@ -1651,7 +1651,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2. poslovni <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3. poslovni imejl <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Da biste otkačili ovaj ekran, dodirnite i zadržite dugmad Nazad i Pregled"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Ova aplikacija ne može da se otkači"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Ekran je zakačen"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Ekran je otkačen"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Traži PIN pre otkačinjanja"</string>
@@ -1817,14 +1816,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Testiranje poruka u hitnim slučajevima"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Odgovori"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM kartica nije prilagođena za glasovne usluge"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM kartica nije podešena za glasovne usluge"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM kartica nije prilagođena za glasovne usluge"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Telefon nije prilagođen za glasovne usluge"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Iskačući prozor"</string>
<string name="slice_more_content" msgid="8504342889413274608">"i još <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Ova prečica zahteva najnoviju aplikaciju"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index f5744ed..8e1b5b4 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"দ্বিতীয় কার্যক্ষেত্র <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"তৃতীয় কার্যক্ষেত্র <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"এই স্ক্রিনটিকে আনপিন করতে ফিরে যাওয়া এবং এক নজরে বোতামদুটি ট্যাপ করে ধরে রাখুন"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"এই অ্যাপটি আনপিন করা যাবে না"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"স্ক্রিন পিন করা হয়েছে"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"পিন না করা স্ক্রীন"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"আনপিন করার আগে পিন চান"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"বিপদকালীন বার্তাগুলির পরীক্ষা"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"উত্তর দিন"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"এই সিম দিয়ে ভয়েস কল করা যাবে না"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"সিমটি ভয়েস কলের জন্য প্রস্তুত নয়"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"এই সিম দিয়ে ভয়েস কল করা যাবে না"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"এই ফোন দিয়ে ভয়েস কল করা যাবে না"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"পপ-আপ উইন্ডো"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>টি"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"এই শর্টকাটটির জন্য লেটেস্ট অ্যাপ প্রয়োজন"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 57dd73f..aa83d1d 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1653,7 +1653,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2. poslovni <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3. poslovni <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Da otkačite ovaj ekran, dodirnite i držite dugmad Nazad i Pregled."</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Ova aplikacija se ne može otkačiti"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Ekran je zakačen"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Ekran je otkačen"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Traži PIN prije nego se otkači"</string>
@@ -1819,14 +1818,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test poruka za hitne slučajeve"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Odgovori"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM kartica nije dozvoljena za govor"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM kartica nije dodijeljena za govor"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM kartica nije dozvoljena za govor"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Telefon nije dozvoljen za govor"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Iskočni prozor"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Za ovu prečicu potrebna je najnovija aplikacija"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 2e0724d..bb793a5 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -275,7 +275,7 @@
<string name="permgroupdesc_storage" msgid="637758554581589203">"accedir a fotos, contingut multimèdia i fitxers del dispositiu"</string>
<string name="permgrouprequest_storage" msgid="7429669910547860218">"Permet que <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> accedeixi a les fotos, al contingut multimèdia i als fitxers del dispositiu"</string>
<string name="permgrouplab_microphone" msgid="171539900250043464">"Micròfon"</string>
- <string name="permgroupdesc_microphone" msgid="4988812113943554584">"enregistrar àudio"</string>
+ <string name="permgroupdesc_microphone" msgid="4988812113943554584">"gravar àudio"</string>
<string name="permgrouprequest_microphone" msgid="8065941268709600606">"Permet que <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> enregistri àudio"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"Càmera"</string>
<string name="permgroupdesc_camera" msgid="3250611594678347720">"fer fotos i vídeos"</string>
@@ -394,12 +394,12 @@
<string name="permdesc_accessCoarseLocation" product="default" msgid="7788009094906196995">"Aquesta aplicació pot obtenir la teva ubicació a partir de fonts de xarxa, com ara torres de telefonia mòbil i xarxes Wi-Fi. Aquests serveis d\'ubicació han d\'estar activats i disponibles al telèfon perquè l\'aplicació els pugui utilitzar."</string>
<string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"canviar la configuració d\'àudio"</string>
<string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permet que l\'aplicació modifiqui la configuració d\'àudio general, com ara el volum i l\'altaveu de sortida que es fa servir."</string>
- <string name="permlab_recordAudio" msgid="3876049771427466323">"enregistrar àudio"</string>
- <string name="permdesc_recordAudio" msgid="4245930455135321433">"Aquesta aplicació pot enregistrar àudio amb el micròfon en qualsevol moment."</string>
+ <string name="permlab_recordAudio" msgid="3876049771427466323">"gravar àudio"</string>
+ <string name="permdesc_recordAudio" msgid="4245930455135321433">"Aquesta aplicació pot gravar àudio amb el micròfon en qualsevol moment."</string>
<string name="permlab_sim_communication" msgid="2935852302216852065">"enviar ordres a la SIM"</string>
<string name="permdesc_sim_communication" msgid="5725159654279639498">"Permet que l\'aplicació enviï ordres a la SIM. Això és molt perillós."</string>
<string name="permlab_camera" msgid="3616391919559751192">"fer fotos i vídeos"</string>
- <string name="permdesc_camera" msgid="5392231870049240670">"Aquesta aplicació pot fer fotos i enregistrar vídeos amb la càmera en qualsevol moment."</string>
+ <string name="permdesc_camera" msgid="5392231870049240670">"Aquesta aplicació pot fer fotos i gravar vídeos amb la càmera en qualsevol moment."</string>
<string name="permlab_vibrate" msgid="7696427026057705834">"controlar la vibració"</string>
<string name="permdesc_vibrate" msgid="6284989245902300945">"Permet que l\'aplicació controli el vibrador."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"trucar directament a números de telèfon"</string>
@@ -450,7 +450,7 @@
<string name="permdesc_changeWifiMulticastState" product="tablet" msgid="7969774021256336548">"Permet que l\'aplicació rebi paquets enviats a tots els dispositius d\'una xarxa Wi-Fi mitjançant les adreces multidifusió, no només a la teva tauleta. Fa servir més energia que el mode que no és multidifusió."</string>
<string name="permdesc_changeWifiMulticastState" product="tv" msgid="9031975661145014160">"Permet que l\'aplicació rebi paquets enviats a tots els dispositius d\'una xarxa Wi-Fi mitjançant les adreces de multidifusió, no només al televisor. Fa servir més energia que el mode que no és de multidifusió."</string>
<string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"Permet que l\'aplicació rebi paquets enviats a tots els dispositius d\'una xarxa Wi-Fi mitjançant les adreces multidifusió, no només al teu telèfon. Fa servir més energia que el mode que no és multidifusió."</string>
- <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"accés a la configuració de Bluetooth"</string>
+ <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"accés a la configuració del Bluetooth"</string>
<string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"Permet que l\'aplicació configuri la tauleta Bluetooth local i que detecti dispositius remots i s\'hi vinculi."</string>
<string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"Permet que l\'aplicació configuri el televisor Bluetooth local, cerqui dispositius remots i s\'hi vinculi."</string>
<string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"Permet que l\'aplicació configuri el telèfon Bluetooth local i que detecti dispositius remots i s\'hi vinculi."</string>
@@ -461,9 +461,9 @@
<string name="permdesc_changeWimaxState" product="tv" msgid="6022307083934827718">"Permet que l\'aplicació connecti el televisor a xarxes WiMAX, o bé que el desconnecti."</string>
<string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"Permet que l\'aplicació connecti i desconnecti el telèfon de les xarxes WiMAX."</string>
<string name="permlab_bluetooth" msgid="6127769336339276828">"vincula amb dispositius Bluetooth"</string>
- <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"Permet que l\'aplicació visualitzi la configuració de Bluetooth de la tauleta i que estableixi i accepti connexions amb dispositius sincronitzats."</string>
- <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"Permet que l\'aplicació consulti la configuració de Bluetooth del televisor i estableixi i accepti connexions amb dispositius vinculats ."</string>
- <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"Permet que una aplicació visualitzi la configuració de Bluetooth del telèfon i que estableixi i accepti connexions amb els dispositius sincronitzats."</string>
+ <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"Permet que l\'aplicació visualitzi la configuració del Bluetooth de la tauleta i que estableixi i accepti connexions amb dispositius sincronitzats."</string>
+ <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"Permet que l\'aplicació consulti la configuració del Bluetooth del televisor i estableixi i accepti connexions amb dispositius vinculats ."</string>
+ <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"Permet que una aplicació visualitzi la configuració del Bluetooth del telèfon i que estableixi i accepti connexions amb els dispositius sincronitzats."</string>
<string name="permlab_nfc" msgid="4423351274757876953">"controlar Comunicació de camp proper (NFC)"</string>
<string name="permdesc_nfc" msgid="7120611819401789907">"Permet que l\'aplicació es comuniqui amb les etiquetes, les targetes i els lectors de Comunicació de camp proper (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3598496301486439258">"desactivació del bloqueig de pantalla"</string>
@@ -1078,14 +1078,14 @@
<string name="sendText" msgid="5209874571959469142">"Tria una acció per al text"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volum del timbre"</string>
<string name="volume_music" msgid="5421651157138628171">"Volum de multimèdia"</string>
- <string name="volume_music_hint_playing_through_bluetooth" msgid="9165984379394601533">"S\'està reproduint a través de Bluetooth"</string>
+ <string name="volume_music_hint_playing_through_bluetooth" msgid="9165984379394601533">"S\'està reproduint per Bluetooth"</string>
<string name="volume_music_hint_silent_ringtone_selected" msgid="8310739960973156272">"S\'ha establert el so de silenci"</string>
<string name="volume_call" msgid="3941680041282788711">"Volum en trucada"</string>
- <string name="volume_bluetooth_call" msgid="2002891926351151534">"Volum en trucada Bluetooth"</string>
+ <string name="volume_bluetooth_call" msgid="2002891926351151534">"Volum en trucada per Bluetooth"</string>
<string name="volume_alarm" msgid="1985191616042689100">"Volum de l\'alarma"</string>
<string name="volume_notification" msgid="2422265656744276715">"Volum de notificacions"</string>
<string name="volume_unknown" msgid="1400219669770445902">"Volum"</string>
- <string name="volume_icon_description_bluetooth" msgid="6538894177255964340">"Volum de Bluetooth"</string>
+ <string name="volume_icon_description_bluetooth" msgid="6538894177255964340">"Volum del Bluetooth"</string>
<string name="volume_icon_description_ringer" msgid="3326003847006162496">"Volum del so"</string>
<string name="volume_icon_description_incall" msgid="8890073218154543397">"Volum de trucada"</string>
<string name="volume_icon_description_media" msgid="4217311719665194215">"Volum de multimèdia"</string>
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2n <xliff:g id="LABEL">%1$s</xliff:g> de la feina"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3r <xliff:g id="LABEL">%1$s</xliff:g> de la feina"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Toca i mantén premuts els botons Enrere i Aplicacions recents per deixar de fixar aquesta pantalla"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"No es pot deixar de fixar aquesta aplicació"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Pantalla fixada"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Fixació de la pantalla anul·lada"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Sol·licita el codi PIN per deixar de fixar"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Prova de missatges d\'emergència"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Respon"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"La SIM no és compatible per a la veu"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"La SIM no està proporcionada per a la veu"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"La SIM no és compatible per a la veu"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"El telèfon no és compatible per a la veu"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Finestra emergent"</string>
<string name="slice_more_content" msgid="8504342889413274608">"<xliff:g id="NUMBER">%1$d</xliff:g> més"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Per fer servir aquesta drecera has de tenir l\'última versió de l\'aplicació"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 336b35d..a131af5 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -979,16 +979,11 @@
<string name="inputMethod" msgid="1653630062304567879">"Input method"</string>
<string name="editTextMenuTitle" msgid="4909135564941815494">"Text actions"</string>
<string name="email" msgid="4560673117055050403">"Email"</string>
- <!-- no translation found for dial (1253998302767701559) -->
- <skip />
- <!-- no translation found for map (6521159124535543457) -->
- <skip />
- <!-- no translation found for browse (1245903488306147205) -->
- <skip />
- <!-- no translation found for sms (4560537514610063430) -->
- <skip />
- <!-- no translation found for add_contact (7867066569670597203) -->
- <skip />
+ <string name="dial" msgid="1253998302767701559">"Call"</string>
+ <string name="map" msgid="6521159124535543457">"Locate"</string>
+ <string name="browse" msgid="1245903488306147205">"Open"</string>
+ <string name="sms" msgid="4560537514610063430">"Message"</string>
+ <string name="add_contact" msgid="7867066569670597203">"Add"</string>
<string name="low_internal_storage_view_title" msgid="5576272496365684834">"Storage space running out"</string>
<string name="low_internal_storage_view_text" msgid="6640505817617414371">"Some system functions may not work"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Not enough storage for the system. Make sure you have 250MB of free space and restart."</string>
@@ -998,7 +993,6 @@
<string name="cancel" msgid="6442560571259935130">"Cancel"</string>
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Cancel"</string>
- <string name="close" msgid="2318214661230355730">"CLOSE"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Attention"</string>
<string name="loading" msgid="7933681260296021180">"Loading…"</string>
<string name="capital_on" msgid="1544682755514494298">"ON"</string>
@@ -1055,8 +1049,6 @@
<string name="screen_compat_mode_scale" msgid="3202955667675944499">"Scale"</string>
<string name="screen_compat_mode_show" msgid="4013878876486655892">"Always show"</string>
<string name="screen_compat_mode_hint" msgid="1064524084543304459">"Re-enable this in System settings > Apps > Downloaded."</string>
- <string name="top_app_killed_title" msgid="6814231368167994497">"App isn\'t responding"</string>
- <string name="top_app_killed_message" msgid="3487519022191609844">"<xliff:g id="APP_NAME">%1$s</xliff:g> may be using too much memory."</string>
<string name="unsupported_display_size_message" msgid="6545327290756295232">"<xliff:g id="APP_NAME">%1$s</xliff:g> does not support the current Display size setting and may behave unexpectedly."</string>
<string name="unsupported_display_size_show" msgid="7969129195360353041">"Always show"</string>
<string name="smv_application" msgid="3307209192155442829">"The app <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has violated its self-enforced StrictMode policy."</string>
@@ -1634,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2nd Work <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3rd Work <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"To unpin this screen, touch & hold Back and Overview buttons"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"This app can\'t be unpinned"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Screen pinned"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Screen unpinned"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Ask for PIN before unpinning"</string>
@@ -1790,18 +1781,14 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Emergency messages test"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Reply"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM not allowed"</string>
- <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM not provisioned"</string>
- <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM not allowed"</string>
- <string name="mmcc_illegal_me" msgid="4438696681169345015">"Phone not allowed"</string>
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM not allowed for voice"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM not provisioned for voice"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM not allowed for voice"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Phone not allowed for voice"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Popup Window"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
- <!-- no translation found for shortcut_restored_on_lower_version (5270675146351613828) -->
- <skip />
- <!-- no translation found for shortcut_restore_not_supported (5028808567940014190) -->
- <skip />
- <!-- no translation found for shortcut_restore_signature_mismatch (2406209324521327518) -->
- <skip />
- <!-- no translation found for shortcut_restore_unknown_issue (8703738064603262597) -->
- <skip />
+ <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"This shortcut requires latest app"</string>
+ <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Couldn’t restore shortcut because app doesn’t support backup and restore"</string>
+ <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Couldn’t restore shortcut because of app signature mismatch"</string>
+ <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Couldn’t restore shortcut"</string>
</resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index a9e8488..dd26fe0 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"<xliff:g id="LABEL">%1$s</xliff:g> de trabajo 2"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"<xliff:g id="LABEL">%1$s</xliff:g> de trabajo 3"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Mantén pulsado el botón Atrás y el de aplicaciones recientes para dejar de fijar esta pantalla"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Esta aplicación no se puede dejar de fijar"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Pantalla fijada"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"La pantalla ya no está fija"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Solicitar PIN para desactivar"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Prueba de mensajes de emergencia"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Responder"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM no permitida para voz"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM no proporcionada para voz"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM no permitida para voz"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Teléfono no permitido para voz"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Ventana emergente"</string>
<string name="slice_more_content" msgid="8504342889413274608">"<xliff:g id="NUMBER">%1$d</xliff:g> más"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Para usar este acceso directo, necesitas la última versión de la aplicación"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 0ba0733d..11a8820 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"दूसरा कार्य <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"तीसरा कार्य <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"इस स्क्रीन को अनपिन करने के लिए, \'वापस जाएं\' और \'खास जानकारी\' के बटन को दबाकर रखें"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"इस ऐप्लिकेशन को अनपिन नहीं किया जा सकता"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"स्क्रीन पिन की गई"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"स्क्रीन अनपिन की गई"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"अनपिन करने से पहले पिन के लिए पूछें"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"आपातकालीन संदेश परीक्षण"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"जवाब दें"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"सिम से कॉल करने की इजाज़त नहीं है"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"सिम से कॉल करने की इजाज़त नहीं है"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"सिम से कॉल करने की इजाज़त नहीं है"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"फ़ोन से कॉल करने की इजाज़त नहीं है"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"पॉपअप विंडो"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"इस शॉर्टकट वाला ऐप चलाने के लिए इसका नया वर्शन डाउनलोड करें"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index c950c40..99ff527 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2-րդ աշխատանք <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3-րդ աշխատանք <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Այս էկրանն ապամրացնելու համար հպեք և պահեք Հետ և Համատեսք կոճակները"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Հնարավոր չէ ապամրացնել այս հավելվածը"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Էկրանն ամրացված է"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Էկրանն ապամրացված է"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Ապաամրացնելուց առաջ հարցնել PIN-կոդը"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Արտակարգ իրավիճակների հաղորդագրությունների թեստ"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Պատասխանել"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"Այս SIM քարտով չեք կարող զանգել"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"Այս SIM քարտը նախապատրաստված չէ զանգելու համար"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"Այս SIM քարտով չեք կարող զանգել"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Այս հեռախոսով չեք կարող զանգել"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Հայտնվող պատուհան"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Այս դյուրանցման համար անհրաժեշտ է հավելվածի վերջին տարբերակը"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index fe515d3..57312e7 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -317,7 +317,7 @@
<string name="permlab_receiveMms" msgid="1821317344668257098">"terima pesan teks (MMS)"</string>
<string name="permdesc_receiveMms" msgid="533019437263212260">"Memungkinkan aplikasi menerima dan memproses pesan MMS. Ini artinya aplikasi dapat memantau atau menghapus pesan yang dikirim ke perangkat Anda tanpa menunjukkannya kepada Anda."</string>
<string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"membaca pesan siaran seluler"</string>
- <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Mengizinkan aplikasi membaca pesan siaran seluler yang diterima perangkat Anda. Lansiran siaran seluler dikirimkan di beberapa lokasi untuk memperingatkan Anda tentang situasi darurat. Aplikasi berbahaya dapat mengganggu kinerja atau operasi perangkat Anda saat siaran seluler darurat diterima."</string>
+ <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Mengizinkan aplikasi membaca pesan siaran seluler yang diterima perangkat Anda. Notifikasi siaran seluler dikirimkan di beberapa lokasi untuk memperingatkan Anda tentang situasi darurat. Aplikasi berbahaya dapat mengganggu kinerja atau operasi perangkat Anda saat siaran seluler darurat diterima."</string>
<string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"baca umpan langganan"</string>
<string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"Mengizinkan apl mendapatkan detail tentang umpan yang saat ini sedang disinkronkan."</string>
<string name="permlab_sendSms" msgid="7544599214260982981">"mengirim dan melihat pesan SMS"</string>
@@ -1384,7 +1384,7 @@
<string name="storage_usb_drive_label" msgid="4501418548927759953">"Drive USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
<string name="storage_usb" msgid="3017954059538517278">"Penyimpanan USB"</string>
<string name="extract_edit_menu_button" msgid="8940478730496610137">"Edit"</string>
- <string name="data_usage_warning_title" msgid="3620440638180218181">"Lansiran penggunaan data"</string>
+ <string name="data_usage_warning_title" msgid="3620440638180218181">"Notifikasi penggunaan data"</string>
<string name="data_usage_warning_body" msgid="6660692274311972007">"Ketuk untuk lihat penggunaan & setelan."</string>
<string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Batas data 2G-3G terlampaui"</string>
<string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Batas data 4G terlampaui"</string>
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"Upaya ke-2 <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"Upaya ke-3 <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Untuk melepas pin layar ini, sentuh & tahan tombol Kembali dan Ringkasan"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Tidak dapat melepas pin aplikasi ini"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Layar disematkan"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Layar dicopot sematannya"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Meminta PIN sebelum melepas sematan"</string>
@@ -1636,8 +1635,8 @@
<string name="package_updated_device_owner" msgid="1847154566357862089">"Diupdate oleh admin Anda"</string>
<string name="package_deleted_device_owner" msgid="2307122077550236438">"Dihapus oleh admin Anda"</string>
<string name="battery_saver_description" msgid="1960431123816253034">"Untuk membantu meningkatkan masa pakai baterai, penghemat baterai mengurangi kinerja perangkat dan membatasi getaran, layanan lokasi, dan sebagian besar data latar belakang. Email, pesan, dan aplikasi lain yang mengandalkan sinkronisasi mungkin tidak diperbarui kecuali jika dibuka.\n\nPenghemat baterai otomatis nonaktif jika perangkat diisi dayanya."</string>
- <string name="data_saver_description" msgid="6015391409098303235">"Untuk membantu mengurangi penggunaan data, Penghemat Data mencegah beberapa aplikasi mengirim atau menerima data di latar belakang. Aplikasi yang sedang digunakan dapat mengakses data, tetapi frekuensinya agak lebih jarang. Misalnya saja, gambar hanya akan ditampilkan setelah disentuh."</string>
- <string name="data_saver_enable_title" msgid="4674073932722787417">"Aktifkan Penghemat Data?"</string>
+ <string name="data_saver_description" msgid="6015391409098303235">"Untuk membantu mengurangi penggunaan data, Penghemat Kuota Internet mencegah beberapa aplikasi mengirim atau menerima data di latar belakang. Aplikasi yang sedang digunakan dapat mengakses data, tetapi frekuensinya agak lebih jarang. Misalnya saja, gambar hanya akan ditampilkan setelah disentuh."</string>
+ <string name="data_saver_enable_title" msgid="4674073932722787417">"Aktifkan Penghemat Kuota Internet?"</string>
<string name="data_saver_enable_button" msgid="7147735965247211818">"Aktifkan"</string>
<plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="4367877408072000848">
<item quantity="other">Selama %1$d menit (hingga <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Tes pesan darurat"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Balas"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM tidak diizinkan untuk suara"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM tidak disediakan untuk suara"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM tidak diizinkan untuk suara"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Ponsel tidak diizinkan untuk suara"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Jendela Pop-up"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Pintasan ini memerlukan aplikasi terbaru"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 5545c43..0f146ba 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1345,7 +1345,7 @@
<string name="vpn_lockdown_disconnected" msgid="735805531187559719">"אין חיבור ל-VPN שפועל כל הזמן"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"שגיאת VPN שמופעל תמיד"</string>
<string name="vpn_lockdown_config" msgid="8151951501116759194">"רוצה לשנות את הגדרות הרשת או הגדרות ה-VPN?"</string>
- <string name="upload_file" msgid="2897957172366730416">"בחר קובץ"</string>
+ <string name="upload_file" msgid="2897957172366730416">"בחירת קובץ"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"לא נבחר קובץ"</string>
<string name="reset" msgid="2448168080964209908">"איפוס"</string>
<string name="submit" msgid="1602335572089911941">"שלח"</string>
@@ -1676,7 +1676,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"<xliff:g id="LABEL">%1$s</xliff:g> שני בעבודה"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"<xliff:g id="LABEL">%1$s</xliff:g> שלישי בעבודה"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"כדי לבטל את ההצמדה של מסך זה, גע בלחצנים \'הקודם\' ו\'סקירה\' והחזק אותם"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"לא ניתן לבטל את ההצמדה של האפליקציה הזו"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"המסך מוצמד"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"הצמדת המסך בוטלה"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"בקש קוד גישה לפני ביטול הצמדה"</string>
@@ -1852,14 +1851,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"בדיקה של הודעות חירום"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"השב"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"כרטיס ה-SIM לא מורשה לזיהוי קולי"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"ניהול התצורה של כרטיס ה-SIM לא מתאים לזיהוי קולי"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"כרטיס ה-SIM לא מורשה לזיהוי קולי"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"הטלפון לא מורשה לזיהוי קולי"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"חלון קופץ"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"קיצור דרך זה דורש את האפליקציה העדכנית ביותר"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index aa2cc3d..1976bbe 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1231,7 +1231,7 @@
<string name="ext_media_unmounting_notification_title" msgid="640674168454809372">"아직 <xliff:g id="NAME">%s</xliff:g> 꺼내는 중…"</string>
<string name="ext_media_unmounting_notification_message" msgid="4182843895023357756">"삭제하지 마세요."</string>
<string name="ext_media_init_action" msgid="7952885510091978278">"설정"</string>
- <string name="ext_media_unmount_action" msgid="1121883233103278199">"꺼내기"</string>
+ <string name="ext_media_unmount_action" msgid="1121883233103278199">"마운트 해제"</string>
<string name="ext_media_browse_action" msgid="8322172381028546087">"둘러보기"</string>
<string name="ext_media_missing_title" msgid="620980315821543904">"<xliff:g id="NAME">%s</xliff:g> 없음"</string>
<string name="ext_media_missing_message" msgid="5761133583368750174">"기기 다시 삽입"</string>
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"두 번째 업무용 <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"세 번째 업무용<xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"이 화면을 고정 해제하려면 \'뒤로\' 및 \'최근 사용\'을 길게 터치하세요."</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"이 앱은 고정 해제할 수 없습니다."</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"화면 고정됨"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"화면 고정 해제됨"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"고정 해제 이전에 PIN 요청"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"긴급 메시지 테스트"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"답장"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM에서 음성이 허용되지 않음"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM이 음성으로 프로비저닝되지 않음"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM에서 음성이 허용되지 않음"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"휴대전화에서 음성이 허용되지 않음"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"팝업 창"</string>
<string name="slice_more_content" msgid="8504342889413274608">"<xliff:g id="NUMBER">%1$d</xliff:g>개 더보기"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"바로가기를 사용하려면 최신 앱이 필요합니다"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 8300237..7de97b3 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1632,7 +1632,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"कार्यालयको दोस्रो <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"कार्यालयको तेस्रो <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"यस स्क्रिनलाई अनपनि गर्न पछाडि जाने र परिदृश्य बटनहरूलाई छोएर थिची राख्नुहोस्"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"यस अनुप्रयोगलाई अनपिन गर्न मिल्दैन"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"स्क्रिन पिन गरियो"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"स्क्रिन अनपिन गरियो"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"पिन निकाल्नुअघि PIN सोध्नुहोस्"</string>
@@ -1788,14 +1787,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"आपतकालीन सन्देशहरूको परीक्षण"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"जवाफ दिनुहोस्"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM मार्फत भ्वाइस कल गर्न मिल्दैन"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM मार्फत भ्वाइस कल गर्ने प्रावधान छैन"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM मार्फत भ्वाइस कल गर्न मिल्दैन"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"फोनमार्फत भ्वाइस कल गर्न मिल्दैन"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"पपअप विन्डो"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"यो सर्टकटलाई पछिल्लो अनुप्रयोग आवश्यक हुन्छ"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 44215c3..f99d531 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -211,9 +211,9 @@
<string name="global_action_lock" msgid="2844945191792119712">"Schermvergrendeling"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Uitschakelen"</string>
<string name="global_action_emergency" msgid="7112311161137421166">"Noodgeval"</string>
- <string name="global_action_bug_report" msgid="7934010578922304799">"Foutenrapport"</string>
- <string name="bugreport_title" msgid="2667494803742548533">"Foutenrapport genereren"</string>
- <string name="bugreport_message" msgid="398447048750350456">"Hiermee worden gegevens over de huidige status van je apparaat verzameld en als e-mail verzonden. Wanneer u een foutenrapport start, duurt het even voordat het kan worden verzonden. Even geduld alstublieft."</string>
+ <string name="global_action_bug_report" msgid="7934010578922304799">"Bugrapport"</string>
+ <string name="bugreport_title" msgid="2667494803742548533">"Bugrapport genereren"</string>
+ <string name="bugreport_message" msgid="398447048750350456">"Hiermee worden gegevens over de huidige status van je apparaat verzameld en als e-mail verzonden. Wanneer u een bugrapport start, duurt het even voordat het kan worden verzonden. Even geduld alstublieft."</string>
<string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactief rapport"</string>
<string name="bugreport_option_interactive_summary" msgid="229299488536107968">"Gebruik deze optie in de meeste situaties. Hiermee kun je de voortgang van het rapport bijhouden, meer gegevens over het probleem opgeven en screenshots maken. Mogelijk worden bepaalde minder vaak gebruikte gedeelten weggelaten (waarvoor het lang zou duren om een rapport te genereren)."</string>
<string name="bugreport_option_full_title" msgid="6354382025840076439">"Volledig rapport"</string>
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2e <xliff:g id="LABEL">%1$s</xliff:g>, werk"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3e <xliff:g id="LABEL">%1$s</xliff:g>, werk"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Tik op Terug en Overzicht en houd deze knoppen vast om dit scherm los te maken"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Deze app kan niet worden losgemaakt"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Scherm vastgezet"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Scherm losgemaakt"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Vraag pin voor losmaken"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test voor noodberichten"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Beantwoorden"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"Simkaart niet toegestaan voor spraak"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"Sim niet geregistreerd voor spraak"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"Simkaart niet toegestaan voor spraak"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Telefoon niet toegestaan voor spraak"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Pop-upvenster"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Voor deze snelkoppeling is de nieuwste app vereist"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index cc6cdc9..d7c10ec 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1676,7 +1676,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"<xliff:g id="LABEL">%1$s</xliff:g> – praca 2"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"<xliff:g id="LABEL">%1$s</xliff:g> – praca 3"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Aby odpiąć ten ekran, naciśnij i przytrzymaj Wstecz oraz Przegląd"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Tej aplikacji nie można odpiąć"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Ekran przypięty"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Ekran odpięty"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Podaj PIN, aby odpiąć"</string>
@@ -1852,14 +1851,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test komunikatów alarmowych"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Odpowiedz"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"Karta SIM jest niedozwolona w przypadku usług głosowych"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"Karta SIM nie jest obsługiwana w przypadku usług głosowych"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"Karta SIM jest niedozwolona w przypadku usług głosowych"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Telefon jest niedozwolony w przypadku usług głosowych"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Wyskakujące okienko"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Ten skrót wymaga zainstalowania najnowszej wersji aplikacji"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index afa28dd..2de83c6 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -166,17 +166,17 @@
<item quantity="other">Autoridades de certificação instaladas</item>
</plurals>
<string name="ssl_ca_cert_noti_by_unknown" msgid="4475437862189850602">"Por um terceiro desconhecido"</string>
- <string name="ssl_ca_cert_noti_by_administrator" msgid="3541729986326153557">"Pelo administrador do seu perfil de trabalho"</string>
+ <string name="ssl_ca_cert_noti_by_administrator" msgid="3541729986326153557">"Pelo gestor do seu perfil de trabalho"</string>
<string name="ssl_ca_cert_noti_managed" msgid="4030263497686867141">"Por <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string>
<string name="work_profile_deleted" msgid="5005572078641980632">"Perfil de trabalho eliminado"</string>
<string name="work_profile_deleted_description" msgid="1100529432509639864">"Perfil de trabalho eliminado devido a aplicação de administração em falta"</string>
- <string name="work_profile_deleted_details" msgid="6307630639269092360">"A aplicação de administração do perfil de trabalho está em falta ou danificada. Consequentemente, o seu perfil de trabalho e os dados relacionados foram eliminados. Contacte o administrador para obter assistência."</string>
+ <string name="work_profile_deleted_details" msgid="6307630639269092360">"A aplicação de administração do perfil de trabalho está em falta ou danificada. Consequentemente, o seu perfil de trabalho e os dados relacionados foram eliminados. Contacte o gestor para obter assistência."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"O seu perfil de trabalho já não está disponível neste dispositivo"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Demasiadas tentativas de introdução da palavra-passe"</string>
<string name="network_logging_notification_title" msgid="6399790108123704477">"O dispositivo é gerido"</string>
<string name="network_logging_notification_text" msgid="7930089249949354026">"A sua entidade gere este dispositivo e pode monitorizar o tráfego de rede. Toque para obter mais detalhes."</string>
<string name="factory_reset_warning" msgid="5423253125642394387">"O seu dispositivo será apagado"</string>
- <string name="factory_reset_message" msgid="7972496262232832457">"Não é possível utilizar a aplicação de administração. O seu dispositivo será agora apagado.\n\nSe tiver questões, contacte o administrador da entidade."</string>
+ <string name="factory_reset_message" msgid="7972496262232832457">"Não é possível utilizar a aplicação de administração. O seu dispositivo será agora apagado.\n\nSe tiver questões, contacte o gestor da entidade."</string>
<string name="me" msgid="6545696007631404292">"Eu"</string>
<string name="power_dialog" product="tablet" msgid="8545351420865202853">"Opções do tablet"</string>
<string name="power_dialog" product="tv" msgid="6153888706430556356">"Opções de TV"</string>
@@ -610,7 +610,7 @@
<item msgid="3145118944639869809">"Personalizado"</item>
</string-array>
<string-array name="organizationTypes">
- <item msgid="7546335612189115615">"Emprego"</item>
+ <item msgid="7546335612189115615">"Trabalho"</item>
<item msgid="4378074129049520373">"Outro"</item>
<item msgid="3455047468583965104">"Personalizado"</item>
</string-array>
@@ -1199,7 +1199,7 @@
<string name="taking_remote_bugreport_notification_title" msgid="6742483073875060934">"A criar relatório de erro…"</string>
<string name="share_remote_bugreport_notification_title" msgid="4987095013583691873">"Pretende partilhar o relatório de erro?"</string>
<string name="sharing_remote_bugreport_notification_title" msgid="7572089031496651372">"A partilhar relatório de erro…"</string>
- <string name="share_remote_bugreport_notification_message_finished" msgid="6029609949340992866">"O seu administrador solicitou um relatório de erro para ajudar na resolução de problemas deste dispositivo. As aplicações e os dados podem ser partilhados."</string>
+ <string name="share_remote_bugreport_notification_message_finished" msgid="6029609949340992866">"O seu gestor solicitou um relatório de erro para ajudar na resolução de problemas deste dispositivo. As aplicações e os dados podem ser partilhados."</string>
<string name="share_remote_bugreport_action" msgid="6249476773913384948">"PARTILHAR"</string>
<string name="decline_remote_bugreport_action" msgid="6230987241608770062">"RECUSAR"</string>
<string name="select_input_method" msgid="8547250819326693584">"Alterar teclado"</string>
@@ -1310,7 +1310,7 @@
<string name="tethered_notification_title" msgid="3146694234398202601">"Ligação ponto a ponto ou hotspot activos"</string>
<string name="tethered_notification_message" msgid="2113628520792055377">"Toque para configurar."</string>
<string name="disable_tether_notification_title" msgid="7526977944111313195">"A ligação (à Internet) via telemóvel está desativada."</string>
- <string name="disable_tether_notification_message" msgid="2913366428516852495">"Contacte o administrador para obter detalhes."</string>
+ <string name="disable_tether_notification_message" msgid="2913366428516852495">"Contacte o gestor para obter detalhes."</string>
<string name="back_button_label" msgid="2300470004503343439">"Anterior"</string>
<string name="next_button_label" msgid="1080555104677992408">"Seguinte"</string>
<string name="skip_button_label" msgid="1275362299471631819">"Ignorar"</string>
@@ -1505,7 +1505,7 @@
<string name="user_logging_out_message" msgid="8939524935808875155">"A terminar a sessão de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="2716755460376028154">"Proprietário"</string>
<string name="error_message_title" msgid="4510373083082500195">"Erro"</string>
- <string name="error_message_change_not_allowed" msgid="1238035947357923497">"O administrador não permite esta alteração"</string>
+ <string name="error_message_change_not_allowed" msgid="1238035947357923497">"O gestor não permite esta alteração"</string>
<string name="app_not_found" msgid="3429141853498927379">"Não foram encontradas aplicações para executar esta ação"</string>
<string name="revoke" msgid="5404479185228271586">"Revogar"</string>
<string name="mediasize_iso_a0" msgid="1994474252931294172">"ISO A0"</string>
@@ -1597,7 +1597,7 @@
<string name="reason_service_unavailable" msgid="7824008732243903268">"Serviço de impressão não ativado"</string>
<string name="print_service_installed_title" msgid="2246317169444081628">"Serviço <xliff:g id="NAME">%s</xliff:g> instalado"</string>
<string name="print_service_installed_message" msgid="5897362931070459152">"Toque para ativar"</string>
- <string name="restr_pin_enter_admin_pin" msgid="8641662909467236832">"Introduzir o PIN do administrador"</string>
+ <string name="restr_pin_enter_admin_pin" msgid="8641662909467236832">"Introduzir o PIN do gestor"</string>
<string name="restr_pin_enter_pin" msgid="3395953421368476103">"Introduzir PIN"</string>
<string name="restr_pin_incorrect" msgid="8571512003955077924">"Incorreto"</string>
<string name="restr_pin_enter_old_pin" msgid="1462206225512910757">"PIN Atual"</string>
@@ -1626,15 +1626,14 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2.º <xliff:g id="LABEL">%1$s</xliff:g> de trabalho"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3.º <xliff:g id="LABEL">%1$s</xliff:g> de trabalho"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Para soltar este ecrã, toque sem soltar nos botões Anterior e Vista geral"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Não é possível soltar esta aplicação"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Ecrã fixo"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Ecrã solto"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pedir PIN antes de soltar"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pedir sequência de desbloqueio antes de soltar"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pedir palavra-passe antes de soltar"</string>
- <string name="package_installed_device_owner" msgid="6875717669960212648">"Instalado pelo seu administrador"</string>
- <string name="package_updated_device_owner" msgid="1847154566357862089">"Atualizado pelo seu administrador"</string>
- <string name="package_deleted_device_owner" msgid="2307122077550236438">"Eliminado pelo seu administrador"</string>
+ <string name="package_installed_device_owner" msgid="6875717669960212648">"Instalado pelo seu gestor"</string>
+ <string name="package_updated_device_owner" msgid="1847154566357862089">"Atualizado pelo seu gestor"</string>
+ <string name="package_deleted_device_owner" msgid="2307122077550236438">"Eliminado pelo seu gestor"</string>
<string name="battery_saver_description" msgid="1960431123816253034">"Para ajudar a melhorar a autonomia da bateria, a poupança de bateria reduz o desempenho do seu dispositivo e limita a vibração, os serviços de localização e a maioria dos dados em segundo plano. O email, as mensagens e outras aplicações que dependem da sincronização não podem ser atualizados exceto se os abrir.\n\nA poupança de bateria desliga-se automaticamente quando o dispositivo está a carregar."</string>
<string name="data_saver_description" msgid="6015391409098303235">"Para ajudar a reduzir a utilização de dados, a Poupança de dados impede que algumas aplicações enviem ou recebam dados em segundo plano. Uma determinada aplicação que esteja a utilizar atualmente pode aceder aos dados, mas é possível que o faça com menos frequência. Isto pode significar, por exemplo, que as imagens não são apresentadas até que toque nas mesmas."</string>
<string name="data_saver_enable_title" msgid="4674073932722787417">"Ativar a Poupança de dados?"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Teste de mensagens de emergência"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Responder"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM não permitido para voz"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM não aprovisionado para voz"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM não permitido para voz"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Telemóvel não permitido para voz"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Janela pop-up"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Este atalho requer a aplicação mais recente."</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 2dd1d1a..b6de548 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -278,8 +278,8 @@
<string name="permgroupdesc_storage" msgid="637758554581589203">"приступа сликама, медијима и датотекама на уређају"</string>
<string name="permgrouprequest_storage" msgid="7429669910547860218">"Дозволите <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> да приступа сликама, медијским датотекама и датотекама на уређају"</string>
<string name="permgrouplab_microphone" msgid="171539900250043464">"Микрофон"</string>
- <string name="permgroupdesc_microphone" msgid="4988812113943554584">"снима аудио"</string>
- <string name="permgrouprequest_microphone" msgid="8065941268709600606">"Дозволите да <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> снима аудио снимке"</string>
+ <string name="permgroupdesc_microphone" msgid="4988812113943554584">"снима звук"</string>
+ <string name="permgrouprequest_microphone" msgid="8065941268709600606">"Дозволите да <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> снима звук"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"Камера"</string>
<string name="permgroupdesc_camera" msgid="3250611594678347720">"снима слике и видео"</string>
<string name="permgrouprequest_camera" msgid="810824326507258410">"Дозволите да <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> снима слике и видео снимке"</string>
@@ -1651,7 +1651,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2. пословни <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3. пословни имејл <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Да бисте откачили овај екран, додирните и задржите дугмад Назад и Преглед"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Ова апликација не може да се откачи"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Екран је закачен"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Екран је откачен"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Тражи PIN пре откачињања"</string>
@@ -1817,14 +1816,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Тестирање порука у хитним случајевима"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Одговори"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM картица није прилагођена за гласовне услуге"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM картица није подешена за гласовне услуге"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM картица није прилагођена за гласовне услуге"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Телефон није прилагођен за гласовне услуге"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Искачући прозор"</string>
<string name="slice_more_content" msgid="8504342889413274608">"и још <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Ова пречица захтева најновију апликацију"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 62b54d2..429ecf1 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2வது பணி <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3வது பணி <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"இந்தத் திரையை அகற்ற, முந்தையது, மேலோட்டப் பார்வை ஆகிய இரண்டு பொத்தானையும் தொட்டுப் பிடித்திருக்கவும்"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"இந்தப் பயன்பாட்டை அகற்ற முடியாது"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"திரை பின் செய்யப்பட்டது"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"திரையின் பின் அகற்றப்பட்டது"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"அகற்றும் முன் PINஐக் கேள்"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"அவசரக் காலச் செய்திகளுக்கான சோதனை"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"பதிலளிக்கும்"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"குரல் அழைப்பை மேற்கொள்ள இந்த சிம்மிற்கு அனுமதி இல்லை"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"குரல் அழைப்பை மேற்கொள்ளும் வசதி இந்த சிம்மிற்கு இல்லை"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"குரல் அழைப்பை மேற்கொள்ள இந்த சிம்மிற்கு அனுமதி இல்லை"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"குரல் அழைப்பை மேற்கொள்ள இந்த ஃபோனுக்கு அனுமதி இல்லை"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"பாப்அப் சாளரம்"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"இந்த ஷார்ட்கட்டைப் பயன்படுத்த, சமீபத்திய பயன்பாடு வேண்டும்"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 2460e65..8b77850 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"İş için 2. <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"İş için 3. <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Bu ekranın sabitlemesini kaldırmak için Geri\'ye ve Genel Bakış\'a dokunup basılı tutun"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Bu uygulamanın sabitlemesi kaldırılamaz"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Ekran sabitlendi"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Ekran sabitlemesi kaldırıldı"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Sabitlemeyi kaldırmadan önce PIN\'i sor"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Acil durum mesajları testi"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Yanıtla"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"Ses için SIM\'e izin verilmiyor"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"Ses için SIM\'in temel hazırlığı yapılmadı"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"Ses için SIM\'e izin verilmiyor"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Ses için telefona izin verilmiyor"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Pop-up Pencere"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Bu kısayol, en son uygulamayı gerektiriyor"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index a99fc0f..af5e0bd 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -980,7 +980,7 @@
<string name="editTextMenuTitle" msgid="4909135564941815494">"Matn yozish"</string>
<string name="email" msgid="4560673117055050403">"E-pochta"</string>
<string name="dial" msgid="1253998302767701559">"Chaqiruv"</string>
- <string name="map" msgid="6521159124535543457">"Joylashuvni aniqlash"</string>
+ <string name="map" msgid="6521159124535543457">"Xaritadan topish"</string>
<string name="browse" msgid="1245903488306147205">"Ochish"</string>
<string name="sms" msgid="4560537514610063430">"Xabar"</string>
<string name="add_contact" msgid="7867066569670597203">"Qo‘shish"</string>
@@ -1626,7 +1626,6 @@
<string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2-ishxona <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3-ishxona <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_toast" msgid="6820571533009838261">"Bu ekrandan chiqish uchun Orqaga va Menyu tugmalarini bosib turing"</string>
- <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Bu ilovani olib tashlab bo‘lmaydi"</string>
<string name="lock_to_app_start" msgid="6643342070839862795">"Ekran qadab qo‘yildi"</string>
<string name="lock_to_app_exit" msgid="8598219838213787430">"Ekran bo‘shatildi"</string>
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Yechishda PIN-kod so‘ralsin"</string>
@@ -1782,14 +1781,10 @@
<string name="etws_primary_default_message_test" msgid="2709597093560037455">"Favqulodda holatlar uchun sinov xabarlari"</string>
<string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Javob berish"</string>
<string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
- <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
- <skip />
- <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
- <skip />
- <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
- <skip />
- <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
- <skip />
+ <string name="mmcc_authentication_reject" msgid="5767701075994754356">"Ovoz uchun SIM karta ishlatish taqiqlangan"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"Ovoz uchun SIM karta taqdim etilmagan"</string>
+ <string name="mmcc_illegal_ms" msgid="807334478177362062">"Ovoz uchun SIM karta ishlatish taqiqlangan"</string>
+ <string name="mmcc_illegal_me" msgid="1950705155760872972">"Ovoz uchun telefon taqiqlangan"</string>
<string name="popup_window_default_title" msgid="4874318849712115433">"Qalqib chiquvchi oyna"</string>
<string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Bu yorliq uchun eng oxirgi versiyadagi ilova zarur"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 773a9b0..0eefec9 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -984,6 +984,8 @@
<attr name="dialogTitleDecorLayout" format="reference" />
<!-- Preferred padding for dialog content. -->
<attr name="dialogPreferredPadding" format="dimension" />
+ <!-- Corner radius of dialogs. -->
+ <attr name="dialogCornerRadius" format="dimension" />
<!-- Theme to use for alert dialogs spawned from this theme. -->
<attr name="alertDialogTheme" format="reference" />
@@ -3654,7 +3656,7 @@
</declare-styleable>
<!-- Specify one or more <code>t3tPmm-filter</code> elements inside a
- <code>host-nfcf-service</code> element to specify a LF_T3T_PMM -->
+ <code>host-nfcf-service</code> element to specify a LF_T3T_PMM. -->
<declare-styleable name="T3tPmmFilter">
<attr name="name" />
@@ -4530,7 +4532,7 @@
<attr name="maxHeight" />
<!-- Makes the TextView be exactly this many lines tall. -->
<attr name="lines" format="integer" min="0" />
- <!-- Makes the TextView be exactly this many pixels tall.
+ <!-- Makes the TextView be exactly this tall.
You could get the same effect by specifying this number in the
layout parameters. -->
<attr name="height" format="dimension" />
@@ -4547,7 +4549,7 @@
<attr name="maxWidth" />
<!-- Makes the TextView be exactly this many ems wide. -->
<attr name="ems" format="integer" min="0" />
- <!-- Makes the TextView be exactly this many pixels wide.
+ <!-- Makes the TextView be exactly this wide.
You could get the same effect by specifying this number in the
layout parameters. -->
<attr name="width" format="dimension" />
@@ -8673,6 +8675,11 @@
<!-- The index of the font in the tcc font file. If the font file referenced is not in the
tcc format, this attribute needs not be specified. -->
<attr name="ttcIndex" format="integer" />
+ <!-- The variation settings to be applied to the font. The string should be in the following
+ format: "'tag1' value1, 'tag2' value2, ...". If the default variation settings should be
+ used, or the font used does not support variation settings, this attribute needs not be
+ specified. -->
+ <attr name="fontVariationSettings" format="string" />
</declare-styleable>
<!-- Attributes that are read when parsing a <fontfamily> tag. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 0e90287..be0f6d9 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1334,7 +1334,7 @@
split that contains the defined component. -->
<attr name="splitName" format="string" />
- <!-- Specifies the target sandbox this app wants to use. Higher sanbox versions
+ <!-- Specifies the target sandbox this app wants to use. Higher sandbox versions
will have increasing levels of security.
<p>The default value of this attribute is <code>1</code>. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 571aad9..dc68341 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -901,6 +901,18 @@
<!-- Maximum color temperature, in Kelvin, supported by Night display. -->
<integer name="config_nightDisplayColorTemperatureMax">4082</integer>
+ <string-array name="config_nightDisplayColorTemperatureCoefficientsNative">
+ <!-- R a-coefficient --> <item>0.0</item>
+ <!-- R b-coefficient --> <item>0.0</item>
+ <!-- R y-intercept --> <item>1.0</item>
+ <!-- G a-coefficient --> <item>-0.00000000962353339</item>
+ <!-- G b-coefficient --> <item>0.000153045476</item>
+ <!-- G y-intercept --> <item>0.390782778</item>
+ <!-- B a-coefficient --> <item>-0.0000000189359041</item>
+ <!-- B b-coefficient --> <item>0.000302412211</item>
+ <!-- B y-intercept --> <item>-0.198650895</item>
+ </string-array>
+
<string-array name="config_nightDisplayColorTemperatureCoefficients">
<!-- R a-coefficient --> <item>0.0</item>
<!-- R b-coefficient --> <item>0.0</item>
@@ -1686,6 +1698,11 @@
a transaction, so it interacts poorly with SECURE_DELETE. -->
<string name="db_default_journal_mode" translatable="false">TRUNCATE</string>
+ <!-- Enables compatibility WAL mode.
+ In this mode, only database journal mode will be changed, connection pool
+ size will still be limited to a single connection. -->
+ <bool name="db_compatibility_wal_supported">true</bool>
+
<!-- Maximum size of the persistent journal file in bytes.
If the journal file grows to be larger than this amount then SQLite will
truncate it after committing the transaction. -->
@@ -1753,9 +1770,6 @@
<string-array name="config_twoDigitNumberPattern" translatable="false">
</string-array>
- <!-- The VoiceMail default value is displayed to my own number if it is true -->
- <bool name="config_telephony_use_own_number_for_voicemail">false</bool>
-
<!-- If this value is true, Sms encoded as octet is decoded by utf8 decoder.
If false, decoded by Latin decoder. -->
<bool name="config_sms_utf8_support">false</bool>
@@ -2073,6 +2087,9 @@
<!-- Maximum number of supported users -->
<integer name="config_multiuserMaximumUsers">1</integer>
+ <!-- Maximum number of users we allow to be running at a time -->
+ <integer name="config_multiuserMaxRunningUsers">3</integer>
+
<!-- Whether UI for multi user should be shown -->
<bool name="config_enableMultiUserUI">false</bool>
@@ -2356,6 +2373,10 @@
property. If this is false, then the following recents config flags are ignored. -->
<bool name="config_hasRecents">true</bool>
+ <!-- Component name for the activity that will be presenting the Recents UI, which will receive
+ special permissions for API related to fetching and presenting recent tasks. -->
+ <string name="config_recentsComponentName" translatable="false">com.android.systemui/.recents.RecentsActivity</string>
+
<!-- The minimum number of visible recent tasks to be presented to the user through the
SystemUI. Can be -1 if there is no minimum limit. -->
<integer name="config_minNumVisibleRecentTasks_grid">-1</integer>
@@ -3131,4 +3152,7 @@
<!-- Component name of media projection permission dialog -->
<string name="config_mediaProjectionPermissionDialogComponent" translateable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
+
+ <!-- Corner radius of system dialogs -->
+ <dimen name="config_dialogCornerRadius">2dp</dimen>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 08e2233..947fcf1 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -525,6 +525,7 @@
<dimen name="magnifier_height">48dp</dimen>
<dimen name="magnifier_elevation">2dp</dimen>
<dimen name="magnifier_offset">42dp</dimen>
+ <item type="dimen" format="float" name="magnifier_zoom_scale">1.25</item>
<dimen name="chooser_grid_padding">0dp</dimen>
<!-- Spacing around the background change frome service to non-service -->
@@ -623,4 +624,7 @@
<dimen name="slice_icon_size">24dp</dimen>
<!-- Standard padding used in a slice view -->
<dimen name="slice_padding">16dp</dimen>
+
+ <!-- Default dialog corner radius -->
+ <dimen name="dialog_corner_radius">2dp</dimen>
</resources>
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index 04ea077..2c4058a 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -27,6 +27,7 @@
<item>ar-DJ</item> <!-- Arabic (Djibouti) -->
<item>ar-DZ</item> <!-- Arabic (Algeria) -->
<item>ar-EG</item> <!-- Arabic (Egypt) -->
+ <item>ar-EG-u-nu-latn</item> <!-- Arabic (Egypt,Western Digits) -->
<item>ar-EH</item> <!-- Arabic (Western Sahara) -->
<item>ar-ER</item> <!-- Arabic (Eritrea) -->
<item>ar-IL</item> <!-- Arabic (Israel) -->
@@ -48,6 +49,7 @@
<item>ar-SY</item> <!-- Arabic (Syria) -->
<item>ar-TD</item> <!-- Arabic (Chad) -->
<item>ar-TN</item> <!-- Arabic (Tunisia) -->
+ <item>ar-TN-u-nu-arab</item> <!-- Arabic (Tunisia,Arabic-Indic Digits) -->
<item>ar-XB</item> <!-- Right-to-left pseudolocale -->
<item>ar-YE</item> <!-- Arabic (Yemen) -->
<item>as-IN</item> <!-- Assamese (India) -->
@@ -366,6 +368,7 @@
<item>ms-SG</item> <!-- Malay (Singapore) -->
<item>mt-MT</item> <!-- Maltese (Malta) -->
<item>my-MM</item> <!-- Burmese (Myanmar (Burma)) -->
+ <item>my-MM-u-nu-latn</item> <!-- Burmese (Myanmar (Burma), Western Digits) -->
<item>mzn-IR</item> <!-- Mazanderani (Iran) -->
<item>naq-NA</item> <!-- Nama (Namibia) -->
<item>nb-NO</item> <!-- Norwegian Bokmål (Norway) -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index b42fd82..fdd56c4 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2846,6 +2846,8 @@
<public-group type="attr" first-id="0x0101056e">
<public name="cantSaveState" />
<public name="ttcIndex" />
+ <public name="fontVariationSettings" />
+ <public name="dialogCornerRadius" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1627a0e..32758e8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -273,7 +273,6 @@
<java-symbol type="bool" name="config_suspendWhenScreenOffDueToProximity" />
<java-symbol type="bool" name="config_swipeDisambiguation" />
<java-symbol type="bool" name="config_syncstorageengine_masterSyncAutomatically" />
- <java-symbol type="bool" name="config_telephony_use_own_number_for_voicemail" />
<java-symbol type="bool" name="config_ui_enableFadingMarquee" />
<java-symbol type="bool" name="config_enableHapticTextHandle" />
<java-symbol type="bool" name="config_use_strict_phone_number_comparation" />
@@ -311,6 +310,7 @@
<java-symbol type="bool" name="config_enableMultiUserUI"/>
<java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
<java-symbol type="bool" name="config_hasRecents" />
+ <java-symbol type="string" name="config_recentsComponentName" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />
@@ -444,6 +444,7 @@
<java-symbol type="integer" name="config_soundEffectVolumeDb" />
<java-symbol type="integer" name="config_lockSoundVolumeDb" />
<java-symbol type="integer" name="config_multiuserMaximumUsers" />
+ <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
<java-symbol type="integer" name="config_safe_media_volume_index" />
<java-symbol type="integer" name="config_mobile_mtu" />
<java-symbol type="array" name="config_mobile_tcp_buffers" />
@@ -666,6 +667,7 @@
<java-symbol type="string" name="date_time" />
<java-symbol type="string" name="date_time_set" />
<java-symbol type="string" name="date_time_done" />
+ <java-symbol type="bool" name="db_compatibility_wal_supported" />
<java-symbol type="string" name="db_default_journal_mode" />
<java-symbol type="string" name="db_default_sync_mode" />
<java-symbol type="string" name="db_wal_sync_mode" />
@@ -2484,6 +2486,7 @@
<java-symbol type="dimen" name="magnifier_width" />
<java-symbol type="dimen" name="magnifier_height" />
<java-symbol type="dimen" name="magnifier_elevation" />
+ <java-symbol type="dimen" name="magnifier_zoom_scale" />
<java-symbol type="dimen" name="magnifier_offset" />
<java-symbol type="string" name="date_picker_prev_month_button" />
@@ -2839,6 +2842,7 @@
<java-symbol type="integer" name="config_nightDisplayColorTemperatureMin" />
<java-symbol type="integer" name="config_nightDisplayColorTemperatureMax" />
<java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficients" />
+ <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficientsNative" />
<!-- Default first user restrictions -->
<java-symbol type="array" name="config_defaultFirstUserRestrictions" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index bf0c906..68d5523 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -88,6 +88,7 @@
<!-- Dialog attributes -->
<item name="dialogTheme">@style/Theme.DeviceDefault.Dialog</item>
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<!-- AlertDialog attributes -->
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
@@ -214,6 +215,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar. This theme
@@ -223,6 +228,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar and
@@ -234,6 +243,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} that has no title bar and translucent
@@ -244,6 +257,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- DeviceDefault theme for dialog windows and activities. This changes the window to be
@@ -263,6 +280,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Dialog} that has a nice minimum width for a
@@ -272,6 +293,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Dialog} without an action bar -->
@@ -280,6 +305,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Dialog_NoActionBar} that has a nice minimum width
@@ -289,6 +318,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
@@ -314,6 +347,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- DeviceDefault theme for a window without an action bar that will be displayed either
@@ -324,6 +361,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- DeviceDefault theme for a presentation window on a secondary display. -->
@@ -332,6 +373,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- DeviceDefault theme for panel windows. This removes all extraneous window
@@ -342,6 +387,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
@@ -351,6 +400,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
@@ -360,6 +413,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- DeviceDefault style for input methods, which is used by the
@@ -369,6 +426,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- DeviceDefault style for input methods, which is used by the
@@ -378,11 +439,19 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
<item name="windowTitleStyle">@style/DialogWindowTitle.DeviceDefault</item>
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -394,6 +463,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<style name="Theme.DeviceDefault.Dialog.NoFrame" parent="Theme.Material.Dialog.NoFrame">
@@ -401,6 +474,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} with a light-colored style -->
@@ -441,6 +518,7 @@
<!-- Dialog attributes -->
<item name="dialogTheme">@style/Theme.DeviceDefault.Light.Dialog</item>
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<!-- AlertDialog attributes -->
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
@@ -562,6 +640,10 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar -->
@@ -570,6 +652,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar.
@@ -579,6 +665,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar
@@ -590,6 +680,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} that has no title bar and translucent
@@ -600,6 +694,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- DeviceDefault light theme for dialog windows and activities. This changes the window to be
@@ -609,6 +707,10 @@
<item name="windowTitleStyle">@style/DialogWindowTitle.DeviceDefault.Light</item>
<item name="windowAnimationStyle">@style/Animation.DeviceDefault.Dialog</item>
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
<item name="buttonBarStyle">@style/DeviceDefault.Light.ButtonBar.AlertDialog</item>
<item name="borderlessButtonStyle">@style/Widget.DeviceDefault.Light.Button.Borderless.Small</item>
@@ -628,6 +730,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} without an action bar -->
@@ -636,6 +742,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog_NoActionBar} that has a nice minimum
@@ -645,6 +755,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
@@ -680,6 +794,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- DeviceDefault light theme for a window without an action bar that will be displayed either
@@ -690,6 +808,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- DeviceDefault light theme for a presentation window on a secondary display. -->
@@ -698,6 +820,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- DeviceDefault light theme for panel windows. This removes all extraneous window
@@ -708,6 +834,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.Alert">
@@ -717,6 +847,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<style name="Theme.DeviceDefault.Light.SearchBar" parent="Theme.Material.Light.SearchBar">
@@ -724,6 +858,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<style name="Theme.DeviceDefault.Light.Voice" parent="Theme.Material.Light.Voice">
@@ -731,6 +869,10 @@
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- DeviceDefault theme for a window that should look like the Settings app. -->
@@ -750,6 +892,10 @@
<item name="navigationBarDividerColor">#1f000000</item>
<item name="navigationBarColor">@android:color/white</item>
<item name="windowLightNavigationBar">true</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- @hide DeviceDefault theme for a window that should use Settings theme colors
@@ -761,6 +907,7 @@
<item name="colorSecondary">@color/secondary_device_default_settings_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
<item name="colorControlNormal">?attr/textColorPrimary</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<style name="Theme.DeviceDefault.QuickSettings.Dialog" parent="Theme.DeviceDefault.Light.Dialog">
@@ -769,6 +916,7 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
<item name="colorSecondary">@color/secondary_device_default_settings_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Settings_Dark} with no action bar -->
@@ -778,6 +926,10 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
</style>
<style name="Theme.DeviceDefault.Settings.Dialog" parent="Theme.Material.Settings.Dialog">
@@ -786,6 +938,10 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<style name="Theme.DeviceDefault.Settings.DialogWhenLarge" parent="Theme.Material.Settings.DialogWhenLarge">
@@ -794,6 +950,10 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
<style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.Alert">
@@ -802,9 +962,13 @@
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_light</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
</style>
- <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar"/>
+ <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" />
<!-- Theme used for the intent picker activity. -->
<style name="Theme.DeviceDefault.Resolver" parent="Theme.Material.Light">
@@ -820,6 +984,10 @@
<item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item>
<item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
@@ -831,7 +999,7 @@
<style name="ThemeOverlay.DeviceDefault" />
- <!-- @hide Theme overlay that inherits from material actionbar, and use accent color for
+ <!-- @hide Theme overlay that inherits from material actionbar, and use accent color for
primary text -->
<style name="ThemeOverlay.DeviceDefault.ActionBar.Accent" parent="ThemeOverlay.Material.ActionBar">
<item name="textColorPrimary">@color/btn_colored_borderless_text_material</item>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index dba4290..c317121 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -186,6 +186,7 @@
<item name="dialogCustomTitleDecorLayout">@layout/dialog_custom_title_material</item>
<item name="dialogTitleDecorLayout">@layout/dialog_title_material</item>
<item name="dialogPreferredPadding">@dimen/dialog_padding_material</item>
+ <item name="dialogCornerRadius">@dimen/dialog_corner_radius</item>
<!-- AlertDialog attributes -->
<item name="alertDialogTheme">@style/ThemeOverlay.Material.Dialog.Alert</item>
@@ -554,6 +555,7 @@
<item name="dialogCustomTitleDecorLayout">@layout/dialog_custom_title_material</item>
<item name="dialogTitleDecorLayout">@layout/dialog_title_material</item>
<item name="dialogPreferredPadding">@dimen/dialog_padding_material</item>
+ <item name="dialogCornerRadius">@dimen/dialog_corner_radius</item>
<!-- AlertDialog attributes -->
<item name="alertDialogTheme">@style/ThemeOverlay.Material.Dialog.Alert</item>
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
index fbaf0f3..0806fa0 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
@@ -20,7 +20,6 @@
import android.test.InstrumentationTestRunner;
import android.test.InstrumentationTestSuite;
-import com.android.connectivitymanagertest.stress.WifiApStress;
import com.android.connectivitymanagertest.stress.WifiStressTest;
import junit.framework.TestSuite;
@@ -35,7 +34,7 @@
*/
public class ConnectivityManagerStressTestRunner extends InstrumentationTestRunner {
- private int mSoftApIterations = 100;
+ private int mSoftApIterations = 0;
private int mScanIterations = 100;
private int mReconnectIterations = 100;
// sleep time before restart wifi, default is set to 2 minutes
@@ -47,7 +46,6 @@
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
- suite.addTestSuite(WifiApStress.class);
suite.addTestSuite(WifiStressTest.class);
return suite;
}
@@ -60,13 +58,6 @@
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- String valueStr = icicle.getString("softap_iterations");
- if (valueStr != null) {
- int iteration = Integer.parseInt(valueStr);
- if (iteration > 0) {
- mSoftApIterations = iteration;
- }
- }
String scanIterationStr = icicle.getString("scan_iterations");
if (scanIterationStr != null) {
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
index 64fed7f..3706e4b 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
@@ -129,12 +129,6 @@
// Get an instance of WifiManager
mWifiManager =(WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
- if (mWifiManager.isWifiApEnabled()) {
- // if soft AP is enabled, disable it
- mWifiManager.setWifiApEnabled(null, false);
- logv("Disable soft ap");
- }
-
// register a connectivity receiver for CONNECTIVITY_ACTION;
mConnectivityReceiver = new ConnectivityReceiver();
mContext.registerReceiver(mConnectivityReceiver,
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java
index 0e57a00..746cb84 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java
@@ -19,7 +19,6 @@
import android.test.InstrumentationTestRunner;
import android.test.InstrumentationTestSuite;
import com.android.connectivitymanagertest.unit.WifiClientTest;
-import com.android.connectivitymanagertest.unit.WifiSoftAPTest;
import junit.framework.TestSuite;
@@ -35,7 +34,6 @@
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
suite.addTestSuite(WifiClientTest.class);
- suite.addTestSuite(WifiSoftAPTest.class);
return suite;
}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java
deleted file mode 100644
index de934b9..0000000
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2010, 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.connectivitymanagertest.stress;
-
-
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.AuthAlgorithm;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiManager;
-import android.os.Environment;
-import android.test.suitebuilder.annotation.LargeTest;
-
-import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner;
-import com.android.connectivitymanagertest.ConnectivityManagerTestBase;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-
-/**
- * Stress test setting up device as wifi hotspot
- */
-public class WifiApStress extends ConnectivityManagerTestBase {
- private static String NETWORK_ID = "AndroidAPTest";
- private static String PASSWD = "androidwifi";
- private final static String OUTPUT_FILE = "WifiStressTestOutput.txt";
- private int mTotalIterations;
- private BufferedWriter mOutputWriter = null;
- private int mLastIteration = 0;
- private boolean mWifiOnlyFlag;
-
- public WifiApStress() {
- super(WifiApStress.class.getSimpleName());
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- ConnectivityManagerStressTestRunner mRunner =
- (ConnectivityManagerStressTestRunner)getInstrumentation();
- mTotalIterations = mRunner.getSoftApInterations();
- mWifiOnlyFlag = mRunner.isWifiOnly();
- turnScreenOn();
- }
-
- @Override
- protected void tearDown() throws Exception {
- // write the total number of iterations into output file
- mOutputWriter = new BufferedWriter(new FileWriter(new File(
- Environment.getExternalStorageDirectory(), OUTPUT_FILE)));
- mOutputWriter.write(String.format("iteration %d out of %d\n",
- mLastIteration + 1, mTotalIterations));
- mOutputWriter.flush();
- mOutputWriter.close();
- super.tearDown();
- }
-
- @LargeTest
- public void testWifiHotSpot() {
- if (mWifiOnlyFlag) {
- logv(getName() + " is excluded for wi-fi only test");
- return;
- }
- WifiConfiguration config = new WifiConfiguration();
- config.SSID = NETWORK_ID;
- config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
- config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
- config.preSharedKey = PASSWD;
-
- // if wifiap enabled, disable it
- assertTrue("failed to disable wifi hotspot",
- mWifiManager.setWifiApEnabled(config, false));
- assertTrue("wifi hotspot not enabled", waitForWifiApState(
- WifiManager.WIFI_AP_STATE_DISABLED, 2 * LONG_TIMEOUT));
-
- // if Wifi is enabled, disable it
- if (mWifiManager.isWifiEnabled()) {
- assertTrue("failed to disable wifi", disableWifi());
- // wait for the wifi state to be DISABLED
- assertTrue("wifi state not disabled", waitForWifiState(
- WifiManager.WIFI_STATE_DISABLED, LONG_TIMEOUT));
- }
- int i;
- for (i = 0; i < mTotalIterations; i++) {
- logv("iteration: " + i);
- mLastIteration = i;
- // enable Wifi tethering
- assertTrue("failed to enable wifi hotspot",
- mWifiManager.setWifiApEnabled(config, true));
- // wait for wifi ap state to be ENABLED
- assertTrue("wifi hotspot not enabled", waitForWifiApState(
- WifiManager.WIFI_AP_STATE_ENABLED, 2 * LONG_TIMEOUT));
- // wait for wifi tethering result
- assertTrue("tether state not changed", waitForTetherStateChange(LONG_TIMEOUT));
- // allow the wifi tethering to be enabled for 10 seconds
- try {
- Thread.sleep(2 * SHORT_TIMEOUT);
- } catch (Exception e) {
- // ignore
- }
- assertTrue("no uplink data connection after Wi-Fi tethering", pingTest());
- // disable wifi hotspot
- assertTrue("failed to disable wifi hotspot",
- mWifiManager.setWifiApEnabled(config, false));
- assertTrue("wifi hotspot not enabled", waitForWifiApState(
- WifiManager.WIFI_AP_STATE_DISABLED, 2 * LONG_TIMEOUT));
- assertFalse("wifi hotspot still enabled", mWifiManager.isWifiApEnabled());
- }
- }
-
-}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java
deleted file mode 100644
index f202862..0000000
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2010 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.connectivitymanagertest.unit;
-
-import android.content.Context;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
-
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.AndroidTestCase;
-
-import android.util.Log;
-
-/**
- * Test Wifi soft AP configuration
- */
-public class WifiSoftAPTest extends AndroidTestCase {
-
- private WifiManager mWifiManager;
- private WifiConfiguration mWifiConfig = null;
- private final String TAG = "WifiSoftAPTest";
- private final int DURATION = 10000;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
- assertNotNull(mWifiManager);
- assertTrue(mWifiManager.setWifiApEnabled(null, true));
- mWifiConfig = mWifiManager.getWifiApConfiguration();
- if (mWifiConfig != null) {
- Log.v(TAG, "mWifiConfig is " + mWifiConfig.toString());
- } else {
- Log.v(TAG, "mWifiConfig is null.");
- }
- }
-
- @Override
- protected void tearDown() throws Exception {
- Log.v(TAG, "turn off wifi tethering");
- mWifiManager.setWifiApEnabled(null, false);
- super.tearDown();
- }
-
- // Test case 1: Test the soft AP SSID with letters
- @LargeTest
- public void testApSsidWithAlphabet() {
- WifiConfiguration config = new WifiConfiguration();
- config.SSID = "abcdefghijklmnopqrstuvwxyz";
- config.allowedKeyManagement.set(KeyMgmt.NONE);
- mWifiConfig = config;
- assertTrue(mWifiManager.setWifiApEnabled(mWifiConfig, true));
- try {
- Thread.sleep(DURATION);
- } catch (InterruptedException e) {
- Log.v(TAG, "exception " + e.getStackTrace());
- assertFalse(true);
- }
- assertNotNull(mWifiManager.getWifiApConfiguration());
- assertEquals("wifi AP state is not enabled", WifiManager.WIFI_AP_STATE_ENABLED,
- mWifiManager.getWifiApState());
- }
-}
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
index 31ce95e..4b32cea 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
@@ -256,13 +256,13 @@
mTestUtils.unpair(mAdapter, device);
mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey,
BluetoothTestRunner.sDevicePairPin);
- mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE, null);
+ mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HID_HOST, null);
for (int i = 0; i < iterations; i++) {
mTestUtils.writeOutput("connectInput iteration " + (i + 1) + " of " + iterations);
- mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE,
+ mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.HID_HOST,
String.format("connectInput(device=%s)", device));
- mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE,
+ mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HID_HOST,
String.format("disconnectInput(device=%s)", device));
}
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
index ee15978..ada0366 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
@@ -227,8 +227,8 @@
case BluetoothProfile.HEADSET:
mConnectionAction = BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED;
break;
- case BluetoothProfile.INPUT_DEVICE:
- mConnectionAction = BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED;
+ case BluetoothProfile.HID_HOST:
+ mConnectionAction = BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED;
break;
case BluetoothProfile.PAN:
mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED;
@@ -322,8 +322,8 @@
case BluetoothProfile.HEADSET:
mHeadset = (BluetoothHeadset) proxy;
break;
- case BluetoothProfile.INPUT_DEVICE:
- mInput = (BluetoothInputDevice) proxy;
+ case BluetoothProfile.HID_HOST:
+ mInput = (BluetoothHidHost) proxy;
break;
case BluetoothProfile.PAN:
mPan = (BluetoothPan) proxy;
@@ -342,7 +342,7 @@
case BluetoothProfile.HEADSET:
mHeadset = null;
break;
- case BluetoothProfile.INPUT_DEVICE:
+ case BluetoothProfile.HID_HOST:
mInput = null;
break;
case BluetoothProfile.PAN:
@@ -362,7 +362,7 @@
private Context mContext;
private BluetoothA2dp mA2dp = null;
private BluetoothHeadset mHeadset = null;
- private BluetoothInputDevice mInput = null;
+ private BluetoothHidHost mInput = null;
private BluetoothPan mPan = null;
/**
@@ -894,7 +894,7 @@
* @param adapter The BT adapter.
* @param device The remote device.
* @param profile The profile to connect. One of {@link BluetoothProfile#A2DP},
- * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}.
+ * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}.
* @param methodName The method name to printed in the logs. If null, will be
* "connectProfile(profile=<profile>, device=<device>)"
*/
@@ -935,8 +935,8 @@
assertTrue(((BluetoothA2dp)proxy).connect(device));
} else if (profile == BluetoothProfile.HEADSET) {
assertTrue(((BluetoothHeadset)proxy).connect(device));
- } else if (profile == BluetoothProfile.INPUT_DEVICE) {
- assertTrue(((BluetoothInputDevice)proxy).connect(device));
+ } else if (profile == BluetoothProfile.HID_HOST) {
+ assertTrue(((BluetoothHidHost)proxy).connect(device));
}
break;
default:
@@ -975,7 +975,7 @@
* @param adapter The BT adapter.
* @param device The remote device.
* @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP},
- * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}.
+ * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}.
* @param methodName The method name to printed in the logs. If null, will be
* "connectProfile(profile=<profile>, device=<device>)"
*/
@@ -1010,8 +1010,8 @@
assertTrue(((BluetoothA2dp)proxy).disconnect(device));
} else if (profile == BluetoothProfile.HEADSET) {
assertTrue(((BluetoothHeadset)proxy).disconnect(device));
- } else if (profile == BluetoothProfile.INPUT_DEVICE) {
- assertTrue(((BluetoothInputDevice)proxy).disconnect(device));
+ } else if (profile == BluetoothProfile.HID_HOST) {
+ assertTrue(((BluetoothHidHost)proxy).disconnect(device));
}
break;
case BluetoothProfile.STATE_DISCONNECTED:
@@ -1237,7 +1237,7 @@
long s = System.currentTimeMillis();
while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
state = mPan.getConnectionState(device);
- if (state == BluetoothInputDevice.STATE_DISCONNECTED
+ if (state == BluetoothHidHost.STATE_DISCONNECTED
&& (receiver.getFiredFlags() & mask) == mask) {
long finish = receiver.getCompletedTime();
if (start != -1 && finish != -1) {
@@ -1255,7 +1255,7 @@
int firedFlags = receiver.getFiredFlags();
removeReceiver(receiver);
fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
- methodName, state, BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask));
+ methodName, state, BluetoothHidHost.STATE_DISCONNECTED, firedFlags, mask));
}
/**
@@ -1404,7 +1404,7 @@
String[] actions = {
BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED,
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED,
- BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED};
+ BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED};
ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile,
expectedFlags);
addReceiver(receiver, actions);
@@ -1443,7 +1443,7 @@
return mHeadset;
}
break;
- case BluetoothProfile.INPUT_DEVICE:
+ case BluetoothProfile.HID_HOST:
if (mInput != null) {
return mInput;
}
@@ -1469,7 +1469,7 @@
sleep(POLL_TIME);
}
return mHeadset;
- case BluetoothProfile.INPUT_DEVICE:
+ case BluetoothProfile.HID_HOST:
while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
sleep(POLL_TIME);
}
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 15eab1f..f2eb872 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -19,7 +19,12 @@
$(call all-java-files-under, src) \
$(call all-Iaidl-files-under, src) \
$(call all-java-files-under, DisabledTestApp/src) \
- $(call all-java-files-under, EnabledTestApp/src)
+ $(call all-java-files-under, EnabledTestApp/src) \
+ $(call all-java-files-under, BinderProxyCountingTestApp/src) \
+ $(call all-java-files-under, BinderProxyCountingTestService/src) \
+ $(call all-Iaidl-files-under, aidl)
+
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl
LOCAL_DX_FLAGS := --core-library
LOCAL_JACK_FLAGS := --multi-dex native
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 9c0543b..51bfc20 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -86,6 +86,7 @@
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+ <uses-permission android:name="android.permission.KILL_UID" />
<!-- location test permissions -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/Android.mk b/core/tests/coretests/BinderProxyCountingTestApp/Android.mk
new file mode 100644
index 0000000..e31d50f
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestApp/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := BinderProxyCountingTestApp
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..a971730
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.binderproxycountingtestapp">
+
+ <application>
+ <service android:name=".BpcTestAppCmdService"
+ android:exported="true"/>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
new file mode 100644
index 0000000..5aae1203
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.binderproxycountingtestapp;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.frameworks.coretests.aidl.IBinderProxyCountingService;
+import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService;
+import com.android.frameworks.coretests.aidl.ITestRemoteCallback;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class BpcTestAppCmdService extends Service {
+ private static final String TAG = BpcTestAppCmdService.class.getSimpleName();
+
+ private static final String TEST_SERVICE_PKG =
+ "com.android.frameworks.coretests.binderproxycountingtestservice";
+ private static final String TEST_SERVICE_CLASS =
+ TEST_SERVICE_PKG + ".BinderProxyCountingService";
+ private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
+
+ private static ServiceConnection mServiceConnection;
+ private static IBinderProxyCountingService mBpcService;
+
+ private IBpcTestAppCmdService.Stub mBinder = new IBpcTestAppCmdService.Stub() {
+
+ private ArrayList<BroadcastReceiver> mBrList = new ArrayList();
+ private ArrayList<ITestRemoteCallback> mTrcList = new ArrayList();
+
+ @Override
+ public void createSystemBinders(int count) {
+ int i = 0;
+ while (i++ < count) {
+ BroadcastReceiver br = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ }
+ };
+ IntentFilter filt = new IntentFilter(Intent.ACTION_POWER_DISCONNECTED);
+ synchronized (mBrList) {
+ mBrList.add(br);
+ }
+ registerReceiver(br, filt);
+ }
+ }
+
+ @Override
+ public void releaseSystemBinders(int count) {
+ int i = 0;
+ while (i++ < count) {
+ BroadcastReceiver br;
+ synchronized (mBrList) {
+ br = mBrList.remove(0);
+ }
+ unregisterReceiver(br);
+ }
+ }
+
+ @Override
+ public void createTestBinders(int count) {
+ int i = 0;
+ while (i++ < count) {
+ ITestRemoteCallback cb = new ITestRemoteCallback.Stub() {};
+ synchronized (mTrcList) {
+ mTrcList.add(cb);
+ }
+ try {
+ mBpcService.registerCallback(cb);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException caught! " + e);
+ }
+ }
+ }
+
+ @Override
+ public void releaseTestBinders(int count) {
+ int i = 0;
+ while (i++ < count) {
+
+ ITestRemoteCallback cb;
+ synchronized (mTrcList) {
+ cb = mTrcList.remove(0);
+ }
+ try {
+ mBpcService.unregisterCallback(cb);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException caught! " + e);
+ }
+ }
+ }
+
+ @Override
+ public void releaseAllBinders() {
+ synchronized (mBrList) {
+ while (mBrList.size() > 0) {
+ unregisterReceiver(mBrList.remove(0));
+ }
+ }
+ synchronized (mTrcList) {
+ while (mTrcList.size() > 0) {
+ try {
+ mBpcService.unregisterCallback(mTrcList.remove(0));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException caught! " + e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public String bindToTestService() {
+ try {
+ final CountDownLatch bindLatch = new CountDownLatch(1);
+ mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "Service connected");
+ mBpcService = IBinderProxyCountingService.Stub.asInterface(service);
+ bindLatch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "Service disconnected");
+ }
+ };
+ final Intent intent = new Intent()
+ .setComponent(new ComponentName(TEST_SERVICE_PKG, TEST_SERVICE_CLASS));
+ bindService(intent, mServiceConnection,
+ Context.BIND_AUTO_CREATE
+ | Context.BIND_ALLOW_OOM_MANAGEMENT
+ | Context.BIND_NOT_FOREGROUND);
+ if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ throw new RuntimeException("Failed to bind to " + TEST_SERVICE_CLASS);
+ }
+ } catch (Exception e) {
+ unbindFromTestService();
+ Log.e(TAG, e.toString());
+ return e.toString();
+ }
+ return null;
+ }
+
+ @Override
+ public void unbindFromTestService() {
+ if (mBpcService != null) {
+ unbindService(mServiceConnection);
+ }
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/BinderProxyCountingTestService/Android.mk b/core/tests/coretests/BinderProxyCountingTestService/Android.mk
new file mode 100644
index 0000000..a63cf0e
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := BinderProxyCountingTestService
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml b/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml
new file mode 100644
index 0000000..777bd20
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.binderproxycountingtestservice">
+
+ <application>
+ <service android:name=".BpcTestServiceCmdService"
+ android:exported="true" />
+ <service android:name=".BinderProxyCountingService"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java
new file mode 100644
index 0000000..41b4c69
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.binderproxycountingtestservice;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+
+import com.android.frameworks.coretests.aidl.IBinderProxyCountingService;
+import com.android.frameworks.coretests.aidl.ITestRemoteCallback;
+
+public class BinderProxyCountingService extends Service {
+ private static final String TAG = BinderProxyCountingService.class.getSimpleName();
+
+ private IBinderProxyCountingService.Stub mBinder = new IBinderProxyCountingService.Stub() {
+
+ final RemoteCallbackList<ITestRemoteCallback> mTestCallbacks = new RemoteCallbackList<>();
+
+ @Override
+ public void registerCallback(ITestRemoteCallback callback) {
+ synchronized (this) {
+ mTestCallbacks.register(callback);
+ }
+ }
+
+ @Override
+ public void unregisterCallback(ITestRemoteCallback callback) {
+ synchronized (this) {
+ mTestCallbacks.unregister(callback);
+ }
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
new file mode 100644
index 0000000..6bed2a2
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.binderproxycountingtestservice;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
+import com.android.frameworks.coretests.aidl.IBpcTestServiceCmdService;
+import com.android.internal.os.BinderInternal;
+
+public class BpcTestServiceCmdService extends Service {
+ private static final String TAG = BpcTestServiceCmdService.class.getSimpleName();
+
+ //ServiceThread mHandlerThread;
+ Handler mHandler;
+ HandlerThread mHandlerThread;
+
+ private IBpcTestServiceCmdService.Stub mBinder = new IBpcTestServiceCmdService.Stub() {
+ IBpcCallbackObserver mCallbackObserver;
+
+ @Override
+ public void forceGc() {
+ int gcCount = Integer.parseInt(Debug.getRuntimeStat("art.gc.gc-count"));
+ int i = 20;
+ while (gcCount == Integer.parseInt(Debug.getRuntimeStat("art.gc.gc-count")) && i > 0) {
+ System.gc();
+ System.runFinalization();
+ i--;
+ }
+ }
+
+ @Override
+ public int getBinderProxyCount(int uid) {
+ return BinderInternal.nGetBinderProxyCount(uid);
+ }
+
+ @Override
+ public void setBinderProxyWatermarks(int high, int low) {
+ BinderInternal.nSetBinderProxyCountWatermarks(high, low);
+ }
+
+ @Override
+ public void enableBinderProxyLimit(boolean enable) {
+ BinderInternal.nSetBinderProxyCountEnabled(enable);
+ }
+
+ @Override
+ public void setBinderProxyCountCallback(IBpcCallbackObserver observer) {
+ if (observer != null) {
+ BinderInternal.setBinderProxyCountCallback(
+ new BinderInternal.BinderProxyLimitListener() {
+ @Override
+ public void onLimitReached(int uid) {
+ try {
+ synchronized (observer) {
+ observer.onCallback(uid);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+ }, mHandler);
+ } else {
+ BinderInternal.clearBinderProxyCountCallback();
+ }
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public void onCreate()
+ {
+ mHandlerThread = new HandlerThread("BinderProxyCountingServiceThread");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/Android.mk b/core/tests/coretests/aidl/Android.mk
new file mode 100644
index 0000000..86e36b6
--- /dev/null
+++ b/core/tests/coretests/aidl/Android.mk
@@ -0,0 +1,22 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-subdir-Iaidl-files)
+LOCAL_MODULE := coretests-aidl
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl
similarity index 62%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl
index a987a16..a69b0c5 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package com.android.frameworks.coretests.aidl;
+import com.android.frameworks.coretests.aidl.ITestRemoteCallback;
-import android.graphics.Bitmap;
-
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
-}
+interface IBinderProxyCountingService {
+ void registerCallback(in ITestRemoteCallback callback);
+ void unregisterCallback(in ITestRemoteCallback callback);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
similarity index 71%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
index a987a16..c4ebd56 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package com.android.frameworks.coretests.aidl;
-import android.graphics.Bitmap;
-
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
-}
+interface IBpcCallbackObserver {
+ void onCallback(int uid);
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl
new file mode 100644
index 0000000..86a0aa0f
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+
+interface IBpcTestAppCmdService {
+ void createSystemBinders(int count);
+ void releaseSystemBinders(int count);
+
+ void createTestBinders(int count);
+ void releaseTestBinders(int count);
+
+ void releaseAllBinders();
+
+ String bindToTestService();
+ void unbindFromTestService();
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
new file mode 100644
index 0000000..abdab41
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
+
+interface IBpcTestServiceCmdService {
+ void forceGc();
+ int getBinderProxyCount(int uid);
+ void setBinderProxyWatermarks(int high, int low);
+ void enableBinderProxyLimit(boolean enable);
+ void setBinderProxyCountCallback(IBpcCallbackObserver observer);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestRemoteCallback.aidl
similarity index 71%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestRemoteCallback.aidl
index a987a16..36bdb6c 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestRemoteCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.app;
+package com.android.frameworks.coretests.aidl;
-import android.graphics.Bitmap;
-
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
-}
+interface ITestRemoteCallback {
+}
\ No newline at end of file
diff --git a/core/tests/coretests/res/font/samplexmlfont.xml b/core/tests/coretests/res/font/samplexmlfont.xml
index 59d615f..f1d14ff 100644
--- a/core/tests/coretests/res/font/samplexmlfont.xml
+++ b/core/tests/coretests/res/font/samplexmlfont.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
- <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/samplefont"
- android:ttcIndex="0"/>
- <font android:fontStyle="italic" android:fontWeight="400" android:font="@font/samplefont2"
- android:ttcIndex="1"/>
- <font android:fontStyle="normal" android:fontWeight="800" android:font="@font/samplefont3"
- android:ttcIndex="2" />
+ <font android:fontStyle="normal" android:fontWeight="400" android:fontVariationSettings="'wdth' 0.8"
+ android:font="@font/samplefont" android:ttcIndex="0"/>
+ <font android:fontStyle="italic" android:fontWeight="400" android:fontVariationSettings="'contrast' 0.5"
+ android:font="@font/samplefont2" android:ttcIndex="1" />
+ <font android:fontStyle="normal" android:fontWeight="800" android:fontVariationSettings="'wdth' 500.0, 'wght' 300.0"
+ android:font="@font/samplefont3" android:ttcIndex="2" />
<font android:fontStyle="italic" android:fontWeight="800" android:font="@font/samplefont4" />
-</font-family>
\ No newline at end of file
+</font-family>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 5457713..bec862a 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -22,10 +22,13 @@
import static org.junit.Assert.assertTrue;
import android.content.Context;
+import android.content.Intent;
import android.media.session.MediaSession;
+import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.widget.RemoteViews;
import org.junit.Before;
import org.junit.Test;
@@ -138,6 +141,44 @@
assertFalse(n.hasCompletedProgress());
}
+ @Test
+ public void allPendingIntents_recollectedAfterReusingBuilder() {
+ PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), 0);
+ PendingIntent intent2 = PendingIntent.getActivity(mContext, 0, new Intent("test2"), 0);
+
+ Notification.Builder builder = new Notification.Builder(mContext, "channel");
+ builder.setContentIntent(intent1);
+
+ Parcel p = Parcel.obtain();
+
+ Notification n1 = builder.build();
+ n1.writeToParcel(p, 0);
+
+ builder.setContentIntent(intent2);
+ Notification n2 = builder.build();
+ n2.writeToParcel(p, 0);
+
+ assertTrue(n2.allPendingIntents.contains(intent2));
+ }
+
+ @Test
+ public void allPendingIntents_containsCustomRemoteViews() {
+ PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent("test"), 0);
+
+ RemoteViews contentView = new RemoteViews(mContext.getPackageName(), 0 /* layoutId */);
+ contentView.setOnClickPendingIntent(1 /* id */, intent);
+
+ Notification.Builder builder = new Notification.Builder(mContext, "channel");
+ builder.setCustomContentView(contentView);
+
+ Parcel p = Parcel.obtain();
+
+ Notification n = builder.build();
+ n.writeToParcel(p, 0);
+
+ assertTrue(n.allPendingIntents.contains(intent));
+ }
+
private Notification.Builder getMediaNotification() {
MediaSession session = new MediaSession(mContext, "test");
return new Notification.Builder(mContext, "color")
diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
index 4d1a9f4..a12a0b8 100644
--- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
+++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
@@ -69,22 +69,26 @@
FontFileResourceEntry font1 = fileEntries[0];
assertEquals(400, font1.getWeight());
assertEquals(0, font1.getItalic());
+ assertEquals("'wdth' 0.8", font1.getVariationSettings());
assertEquals(0, font1.getTtcIndex());
assertEquals("res/font/samplefont.ttf", font1.getFileName());
FontFileResourceEntry font2 = fileEntries[1];
assertEquals(400, font2.getWeight());
assertEquals(1, font2.getItalic());
assertEquals(1, font2.getTtcIndex());
+ assertEquals("'contrast' 0.5", font2.getVariationSettings());
assertEquals("res/font/samplefont2.ttf", font2.getFileName());
FontFileResourceEntry font3 = fileEntries[2];
assertEquals(800, font3.getWeight());
assertEquals(0, font3.getItalic());
assertEquals(2, font3.getTtcIndex());
+ assertEquals("'wdth' 500.0, 'wght' 300.0", font3.getVariationSettings());
assertEquals("res/font/samplefont3.ttf", font3.getFileName());
FontFileResourceEntry font4 = fileEntries[3];
assertEquals(800, font4.getWeight());
assertEquals(1, font4.getItalic());
assertEquals(0, font4.getTtcIndex());
+ assertEquals(null, font4.getVariationSettings());
assertEquals("res/font/samplefont4.ttf", font4.getFileName());
}
diff --git a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
new file mode 100644
index 0000000..6cdb35ab
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
+import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService;
+import com.android.frameworks.coretests.aidl.IBpcTestServiceCmdService;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for verifying the Binder Proxy Counting and Limiting.
+ *
+ * To manually build and install relevant test apps
+ *
+ * Build:
+ * mmma frameworks/base/core/tests/coretests/BinderProxyCountingTestApp
+ * mmma frameworks/base/core/tests/coretests/BinderProxyCountingTestService
+ * Install:
+ * adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/BinderProxyCountingTestApp/BinderProxyCountingTestApp.apk
+ * adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/BinderProxyCountingTestService/BinderProxyCountingTestService.apk
+ *
+ * To run the tests, use
+ *
+ * Build: m FrameworksCoreTests
+ * Install: adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
+ * Run: adb shell am instrument -e class android.os.BinderProxyCountingTest -w \
+ * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+ *
+ * or
+ *
+ * bit FrameworksCoreTests:android.os.BinderProxyCountingTest
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BinderProxyCountingTest {
+ private static final String TAG = BinderProxyCountingTest.class.getSimpleName();
+
+ private static final String TEST_APP_PKG =
+ "com.android.frameworks.coretests.binderproxycountingtestapp";
+ private static final String TEST_APP_CMD_SERVICE = TEST_APP_PKG + ".BpcTestAppCmdService";
+ private static final String TEST_SERVICE_PKG =
+ "com.android.frameworks.coretests.binderproxycountingtestservice";
+ private static final String TEST_SERVICE_CMD_SERVICE =
+ TEST_SERVICE_PKG + ".BpcTestServiceCmdService";
+
+ private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
+ private static final int TOO_MANY_BINDERS_TIMEOUT_SEC = 2;
+
+ // Keep in sync with sBinderProxyCountLimit in BpBinder.cpp
+ private static final int BINDER_PROXY_LIMIT = 2500;
+
+ private static Context sContext;
+ private static UiDevice sUiDevice;
+
+ private static ServiceConnection sTestAppConnection;
+ private static ServiceConnection sTestServiceConnection;
+ private static IBpcTestAppCmdService sBpcTestAppCmdService;
+ private static IBpcTestServiceCmdService sBpcTestServiceCmdService;
+ private static final Intent sTestAppIntent = new Intent()
+ .setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_CMD_SERVICE));
+ private static final Intent sTestServiceIntent = new Intent()
+ .setComponent(new ComponentName(TEST_SERVICE_PKG, TEST_SERVICE_CMD_SERVICE));
+ private static final Consumer<IBinder> sTestAppConsumer = (service) -> {
+ sBpcTestAppCmdService = IBpcTestAppCmdService.Stub.asInterface(service);
+ };
+ private static final Consumer<IBinder> sTestServiceConsumer = (service) -> {
+ sBpcTestServiceCmdService = IBpcTestServiceCmdService.Stub.asInterface(service);
+ };
+ private static int sTestPkgUid;
+
+ /**
+ * Setup any common data for the upcoming tests.
+ */
+ @BeforeClass
+ public static void setUpOnce() throws Exception {
+ sContext = InstrumentationRegistry.getContext();
+ sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0);
+ ((ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE)).killUid(sTestPkgUid,
+ "Wiping Test Package");
+
+ sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ private ServiceConnection bindService(final Consumer<IBinder> consumer, Intent intent)
+ throws Exception {
+ final CountDownLatch bindLatch = new CountDownLatch(1);
+ ServiceConnection connection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "Service connected");
+ consumer.accept(service);
+ bindLatch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "Service disconnected");
+ }
+ };
+ sContext.bindService(intent, connection,
+ Context.BIND_AUTO_CREATE
+ | Context.BIND_ALLOW_OOM_MANAGEMENT
+ | Context.BIND_NOT_FOREGROUND);
+ if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for the service to bind in " + sTestPkgUid);
+ }
+ return connection;
+ }
+
+
+ private void unbindService(ServiceConnection service) {
+ if (service != null) {
+ sContext.unbindService(service);
+ }
+ }
+
+ private void bindTestAppToTestService() throws Exception {
+ if (sBpcTestAppCmdService != null) {
+ String errorMessage = sBpcTestAppCmdService.bindToTestService();
+ if (errorMessage != null) {
+ fail(errorMessage);
+ }
+ }
+ }
+
+ private void unbindTestAppFromTestService() throws Exception {
+ if (sBpcTestAppCmdService != null) {
+ sBpcTestAppCmdService.unbindFromTestService();
+ }
+ }
+
+ private CountDownLatch createBinderLimitLatch() throws RemoteException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ sBpcTestServiceCmdService.setBinderProxyCountCallback(
+ new IBpcCallbackObserver.Stub() {
+ @Override
+ public void onCallback(int uid) {
+ if (uid == sTestPkgUid) {
+ latch.countDown();
+ }
+ }
+ });
+ return latch;
+ }
+
+ /**
+ * Get the Binder Proxy count held by SYSTEM for a given uid
+ */
+ private int getSystemBinderCount(int uid) throws Exception {
+ return Integer.parseInt(sUiDevice.executeShellCommand(
+ "dumpsys activity binder-proxies " + uid).trim());
+ }
+
+ @Test
+ public void testBinderProxyCount() throws Exception {
+ // Arbitrary list of Binder create and release
+ // Should cumulatively equal 0 and must never add up past the binder limit at any point
+ int[] testValues = {223, -103, -13, 25, 90, -222};
+ try {
+ sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+ // Get the baseline of binders naturally held by the test Package
+ int expectedBinderCount = getSystemBinderCount(sTestPkgUid);
+
+ for (int testValue : testValues) {
+ if (testValue > 0) {
+ sBpcTestAppCmdService.createSystemBinders(testValue);
+ } else {
+ sBpcTestAppCmdService.releaseSystemBinders(-testValue);
+ }
+ expectedBinderCount += testValue;
+ int currentBinderCount = getSystemBinderCount(sTestPkgUid);
+ assertEquals("Current Binder Count (" + currentBinderCount
+ + ") does not equal expected Binder Count (" + expectedBinderCount
+ + ")", expectedBinderCount, currentBinderCount);
+ }
+ } finally {
+ unbindService(sTestAppConnection);
+ }
+ }
+
+ @Test
+ public void testBinderProxyLimitBoundary() throws Exception {
+ final int binderProxyLimit = 2000;
+ final int rearmThreshold = 1800;
+ try {
+ sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+ sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
+ bindTestAppToTestService();
+ sBpcTestServiceCmdService.enableBinderProxyLimit(true);
+
+ sBpcTestServiceCmdService.forceGc();
+ // Get the baseline of binders naturally held by the test Package
+ int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+
+ final CountDownLatch binderLimitLatch = createBinderLimitLatch();
+ sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
+
+ // Create Binder Proxies up to the limit
+ sBpcTestAppCmdService.createTestBinders(binderProxyLimit - baseBinderCount);
+ if (binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+ + " when proxy limit should not have been reached");
+ }
+
+ // Create one more Binder to cross the limit
+ sBpcTestAppCmdService.createTestBinders(1);
+ if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+ }
+
+ sBpcTestAppCmdService.releaseAllBinders();
+ } finally {
+ unbindTestAppFromTestService();
+ unbindService(sTestAppConnection);
+ unbindService(sTestServiceConnection);
+ }
+ }
+
+ @Test
+ public void testSetBinderProxyLimit() throws Exception {
+ int[] testLimits = {1000, 222, 800};
+ try {
+ sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+ sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
+ bindTestAppToTestService();
+ sBpcTestServiceCmdService.enableBinderProxyLimit(true);
+
+ sBpcTestServiceCmdService.forceGc();
+ int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+ for (int testLimit : testLimits) {
+ final CountDownLatch binderLimitLatch = createBinderLimitLatch();
+ // Change the BinderProxyLimit
+ sBpcTestServiceCmdService.setBinderProxyWatermarks(testLimit, baseBinderCount + 10);
+ // Exceed the new Binder Proxy Limit
+ sBpcTestAppCmdService.createTestBinders(testLimit + 1);
+ if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+ }
+
+ sBpcTestAppCmdService.releaseTestBinders(testLimit + 1);
+ sBpcTestServiceCmdService.forceGc();
+ }
+ } finally {
+ unbindTestAppFromTestService();
+ unbindService(sTestAppConnection);
+ unbindService(sTestServiceConnection);
+ }
+ }
+
+ @Test
+ public void testRearmCallbackThreshold() throws Exception {
+ final int binderProxyLimit = 2000;
+ final int exceedBinderProxyLimit = binderProxyLimit + 10;
+ final int rearmThreshold = 1800;
+ try {
+ sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+ sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
+ bindTestAppToTestService();
+ sBpcTestServiceCmdService.enableBinderProxyLimit(true);
+
+ sBpcTestServiceCmdService.forceGc();
+ final CountDownLatch firstBinderLimitLatch = createBinderLimitLatch();
+ sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
+ // Exceed the Binder Proxy Limit
+ sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit);
+ if (!firstBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+ }
+
+ sBpcTestServiceCmdService.forceGc();
+ int currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+ // Drop to the threshold, this should not rearm the callback
+ sBpcTestAppCmdService.releaseTestBinders(currentBinderCount - rearmThreshold);
+
+ sBpcTestServiceCmdService.forceGc();
+ currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+
+ final CountDownLatch secondBinderLimitLatch = createBinderLimitLatch();
+ // Exceed the Binder Proxy limit which should not cause a callback since there has
+ // been no rearm
+ sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
+ if (secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+ + " when the callback has not been rearmed yet");
+ }
+
+ sBpcTestServiceCmdService.forceGc();
+ currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+ // Drop below the rearmThreshold to rearm the BinderProxyLimitCallback
+ sBpcTestAppCmdService.releaseTestBinders(currentBinderCount - rearmThreshold + 1);
+
+ sBpcTestServiceCmdService.forceGc();
+ currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+ // Exceed the Binder Proxy limit for the last time
+ sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
+
+ if (!secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+ }
+ sBpcTestAppCmdService.releaseTestBinders(currentBinderCount);
+ } finally {
+ unbindTestAppFromTestService();
+ unbindService(sTestAppConnection);
+ unbindService(sTestServiceConnection);
+ }
+ }
+
+ @Test
+ public void testKillBadBehavingApp() throws Exception {
+ final CountDownLatch binderDeathLatch = new CountDownLatch(1);
+ final int exceedBinderProxyLimit = BINDER_PROXY_LIMIT + 1;
+
+ try {
+ sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+ sBpcTestAppCmdService.asBinder().linkToDeath(new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Log.v(TAG, "BpcTestAppCmdService died!");
+ binderDeathLatch.countDown();
+ }
+ }, 0);
+ try {
+ // Exceed the Binder Proxy Limit emulating a bad behaving app
+ sBpcTestAppCmdService.createSystemBinders(exceedBinderProxyLimit);
+ } catch (DeadObjectException doe) {
+ // We are expecting the service to get killed mid call, so a DeadObjectException
+ // is not unexpected
+ }
+
+ if (!binderDeathLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ sBpcTestAppCmdService.releaseSystemBinders(exceedBinderProxyLimit);
+ fail("Timed out waiting for uid " + sTestPkgUid + " to die.");
+ }
+
+ } finally {
+ unbindService(sTestAppConnection);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
new file mode 100644
index 0000000..f8d6884
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.filters.Suppress;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextLineTest {
+ private boolean stretchesToFullWidth(CharSequence line) {
+ final TextPaint paint = new TextPaint();
+ final TextLine tl = TextLine.obtain();
+ tl.set(paint, line, 0, line.length(), Layout.DIR_LEFT_TO_RIGHT,
+ Layout.DIRS_ALL_LEFT_TO_RIGHT, false /* hasTabs */, null /* tabStops */);
+ final float originalWidth = tl.metrics(null);
+ final float expandedWidth = 2 * originalWidth;
+
+ tl.justify(expandedWidth);
+ final float newWidth = tl.metrics(null);
+ TextLine.recycle(tl);
+ return Math.abs(newWidth - expandedWidth) < 0.5;
+ }
+
+ @Test
+ public void testJustify_spaces() {
+ // There are no spaces to stretch.
+ assertFalse(stretchesToFullWidth("text"));
+
+ assertTrue(stretchesToFullWidth("one space"));
+ assertTrue(stretchesToFullWidth("exactly two spaces"));
+ assertTrue(stretchesToFullWidth("up to three spaces"));
+ }
+
+ // NBSP should also stretch when it's not used as a base for a combining mark. This doesn't work
+ // yet (b/68204709).
+ @Suppress
+ public void disabledTestJustify_NBSP() {
+ final char nbsp = '\u00A0';
+ assertTrue(stretchesToFullWidth("non-breaking" + nbsp + "space"));
+ assertTrue(stretchesToFullWidth("mix" + nbsp + "and match"));
+
+ final char combining_acute = '\u0301';
+ assertFalse(stretchesToFullWidth("combining" + nbsp + combining_acute + "acute"));
+ }
+}
diff --git a/core/tests/coretests/src/android/util/ScrollViewScenario.java b/core/tests/coretests/src/android/util/ScrollViewScenario.java
index db3d9d0..b5140e3 100644
--- a/core/tests/coretests/src/android/util/ScrollViewScenario.java
+++ b/core/tests/coretests/src/android/util/ScrollViewScenario.java
@@ -16,8 +16,6 @@
package android.util;
-import com.google.android.collect.Lists;
-
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
@@ -29,6 +27,8 @@
import android.widget.ScrollView;
import android.widget.TextView;
+import com.google.android.collect.Lists;
+
import java.util.List;
/**
@@ -269,5 +269,6 @@
mScrollView.setSmoothScrollingEnabled(false);
setContentView(mScrollView);
+ mScrollView.restoreDefaultFocus();
}
}
diff --git a/core/tests/coretests/src/android/view/DisabledLongpressTest.java b/core/tests/coretests/src/android/view/DisabledLongpressTest.java
index 3123897..faa0865 100644
--- a/core/tests/coretests/src/android/view/DisabledLongpressTest.java
+++ b/core/tests/coretests/src/android/view/DisabledLongpressTest.java
@@ -16,17 +16,15 @@
package android.view;
-import android.view.Longpress;
-import com.android.frameworks.coretests.R;
-import android.util.KeyUtils;
+import android.test.ActivityInstrumentationTestCase;
import android.test.TouchUtils;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
-
-import android.test.ActivityInstrumentationTestCase;
-import android.view.View;
+import android.util.KeyUtils;
import android.view.View.OnLongClickListener;
+import com.android.frameworks.coretests.R;
+
/**
* Exercises {@link android.view.View}'s longpress plumbing by testing the
* disabled case.
@@ -34,7 +32,7 @@
public class DisabledLongpressTest extends ActivityInstrumentationTestCase<Longpress> {
private View mSimpleView;
private boolean mLongClicked;
-
+
public DisabledLongpressTest() {
super("com.android.frameworks.coretests", Longpress.class);
}
@@ -66,16 +64,15 @@
@MediumTest
public void testSetUpConditions() throws Exception {
assertNotNull(mSimpleView);
- assertTrue(mSimpleView.hasFocus());
assertFalse(mLongClicked);
}
@LargeTest
public void testKeypadLongClick() throws Exception {
- mSimpleView.requestFocus();
+ getActivity().runOnUiThread(() -> mSimpleView.requestFocus());
getInstrumentation().waitForIdleSync();
KeyUtils.longClick(this);
-
+
getInstrumentation().waitForIdleSync();
assertFalse(mLongClicked);
}
diff --git a/core/tests/coretests/src/android/view/LongpressTest.java b/core/tests/coretests/src/android/view/LongpressTest.java
index 45ce331..d3d7589 100644
--- a/core/tests/coretests/src/android/view/LongpressTest.java
+++ b/core/tests/coretests/src/android/view/LongpressTest.java
@@ -16,24 +16,22 @@
package android.view;
-import android.view.Longpress;
-import com.android.frameworks.coretests.R;
-import android.util.KeyUtils;
+import android.test.ActivityInstrumentationTestCase;
import android.test.TouchUtils;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
-
-import android.test.ActivityInstrumentationTestCase;
-import android.view.View;
+import android.util.KeyUtils;
import android.view.View.OnLongClickListener;
+import com.android.frameworks.coretests.R;
+
/**
* Exercises {@link android.view.View}'s longpress plumbing.
*/
public class LongpressTest extends ActivityInstrumentationTestCase<Longpress> {
private View mSimpleView;
private boolean mLongClicked;
-
+
public LongpressTest() {
super("com.android.frameworks.coretests", Longpress.class);
}
@@ -62,16 +60,15 @@
@MediumTest
public void testSetUpConditions() throws Exception {
assertNotNull(mSimpleView);
- assertTrue(mSimpleView.hasFocus());
assertFalse(mLongClicked);
}
@LargeTest
public void testKeypadLongClick() throws Exception {
- mSimpleView.requestFocus();
+ getActivity().runOnUiThread(() -> mSimpleView.requestFocus());
getInstrumentation().waitForIdleSync();
KeyUtils.longClick(this);
-
+
getInstrumentation().waitForIdleSync();
assertTrue(mLongClicked);
}
diff --git a/core/tests/coretests/src/android/view/VisibilityTest.java b/core/tests/coretests/src/android/view/VisibilityTest.java
index 17d2e3e..29c1c8a 100644
--- a/core/tests/coretests/src/android/view/VisibilityTest.java
+++ b/core/tests/coretests/src/android/view/VisibilityTest.java
@@ -16,16 +16,16 @@
package android.view;
-import android.view.Visibility;
-import com.android.frameworks.coretests.R;
+import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
import android.test.ActivityInstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.widget.Button;
import android.widget.TextView;
-import android.view.View;
-import static android.view.KeyEvent.*;
+
+import com.android.frameworks.coretests.R;
/**
* Exercises {@link android.view.View}'s ability to change visibility between
@@ -64,14 +64,12 @@
assertNotNull(mVisible);
assertNotNull(mInvisible);
assertNotNull(mGone);
-
- assertTrue(mVisible.hasFocus());
}
@MediumTest
public void testVisibleToInvisible() throws Exception {
- sendKeys("DPAD_RIGHT");
- assertTrue(mInvisible.hasFocus());
+ getActivity().runOnUiThread(() -> mInvisible.requestFocus());
+ getInstrumentation().waitForIdleSync();
int oldTop = mVictim.getTop();
@@ -84,9 +82,8 @@
@MediumTest
public void testVisibleToGone() throws Exception {
- //sendKeys("2*DPAD_RIGHT");
- sendRepeatedKeys(2, KEYCODE_DPAD_RIGHT);
- assertTrue(mGone.hasFocus());
+ getActivity().runOnUiThread(() -> mGone.requestFocus());
+ getInstrumentation().waitForIdleSync();
int oldTop = mVictim.getTop();
@@ -99,8 +96,8 @@
@LargeTest
public void testGoneToVisible() throws Exception {
- sendKeys("2*DPAD_RIGHT");
- assertTrue(mGone.hasFocus());
+ getActivity().runOnUiThread(() -> mGone.requestFocus());
+ getInstrumentation().waitForIdleSync();
int oldTop = mVictim.getTop();
@@ -119,8 +116,8 @@
@MediumTest
public void testGoneToInvisible() throws Exception {
- sendKeys("2*DPAD_RIGHT");
- assertTrue(mGone.hasFocus());
+ getActivity().runOnUiThread(() -> mGone.requestFocus());
+ getInstrumentation().waitForIdleSync();
int oldTop = mVictim.getTop();
@@ -139,8 +136,8 @@
@MediumTest
public void testInvisibleToVisible() throws Exception {
- sendKeys("DPAD_RIGHT");
- assertTrue(mInvisible.hasFocus());
+ getActivity().runOnUiThread(() -> mInvisible.requestFocus());
+ getInstrumentation().waitForIdleSync();
int oldTop = mVictim.getTop();
@@ -159,8 +156,8 @@
@MediumTest
public void testInvisibleToGone() throws Exception {
- sendKeys("DPAD_RIGHT");
- assertTrue(mInvisible.hasFocus());
+ getActivity().runOnUiThread(() -> mInvisible.requestFocus());
+ getInstrumentation().waitForIdleSync();
int oldTop = mVictim.getTop();
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index ddf9876..70cf097 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -27,6 +27,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
+import android.os.Binder;
import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -376,13 +377,24 @@
parcelAndRecreate(views);
}
- private void parcelAndRecreate(RemoteViews views) {
- Parcel p = Parcel.obtain();
- views.writeToParcel(p, 0);
- p.setDataPosition(0);
+ private RemoteViews parcelAndRecreate(RemoteViews views) {
+ return parcelAndRecreateWithPendingIntentCookie(views, null);
+ }
- RemoteViews.CREATOR.createFromParcel(p);
- p.recycle();
+ private RemoteViews parcelAndRecreateWithPendingIntentCookie(RemoteViews views, Object cookie) {
+ Parcel p = Parcel.obtain();
+ try {
+ views.writeToParcel(p, 0);
+ p.setDataPosition(0);
+
+ if (cookie != null) {
+ p.setClassCookie(PendingIntent.class, cookie);
+ }
+
+ return RemoteViews.CREATOR.createFromParcel(p);
+ } finally {
+ p.recycle();
+ }
}
@Test
@@ -399,4 +411,37 @@
throw new Exception(t);
}
}
+
+ @Test
+ public void copy_keepsPendingIntentWhitelistToken() throws Exception {
+ Binder whitelistToken = new Binder();
+
+ RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
+ PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
+ new Intent("test"), PendingIntent.FLAG_ONE_SHOT);
+ views.setOnClickPendingIntent(1, pi);
+ RemoteViews withCookie = parcelAndRecreateWithPendingIntentCookie(views, whitelistToken);
+
+ RemoteViews cloned = new RemoteViews(withCookie);
+
+ PendingIntent found = extractAnyPendingIntent(cloned);
+ assertEquals(whitelistToken, found.getWhitelistToken());
+ }
+
+ private PendingIntent extractAnyPendingIntent(RemoteViews cloned) {
+ PendingIntent[] found = new PendingIntent[1];
+ Parcel p = Parcel.obtain();
+ try {
+ PendingIntent.setOnMarshaledListener((intent, parcel, flags) -> {
+ if (parcel == p) {
+ found[0] = intent;
+ }
+ });
+ cloned.writeToParcel(p, 0);
+ } finally {
+ p.recycle();
+ PendingIntent.setOnMarshaledListener(null);
+ }
+ return found[0];
+ }
}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 3e03481..bbdbdb1 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -26,12 +26,10 @@
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates;
-import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
import static android.widget.espresso.DragHandleUtils.onHandleView;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsNotDisplayed;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex;
import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
@@ -61,6 +59,7 @@
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.Suppress;
import android.text.InputType;
import android.text.Selection;
import android.text.Spannable;
@@ -216,7 +215,6 @@
onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
onView(withId(R.id.textview)).check(hasSelection(""));
- assertNoSelectionHandles();
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
// Test undo returns to the original state.
@@ -269,18 +267,12 @@
@Test
public void testToolbarAppearsAfterSelection() {
final String text = "Toolbar appears after selection.";
- assertFloatingToolbarIsNotDisplayed();
onView(withId(R.id.textview)).perform(replaceText(text));
onView(withId(R.id.textview)).perform(
longPressOnTextAtIndex(text.indexOf("appears")));
sleepForFloatingToolbarPopup();
assertFloatingToolbarIsDisplayed();
-
- final String text2 = "Toolbar disappears after typing text.";
- onView(withId(R.id.textview)).perform(replaceText(text2));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsNotDisplayed();
}
@Test
@@ -310,7 +302,6 @@
@Test
public void testToolbarAndInsertionHandle() {
final String text = "text";
- assertFloatingToolbarIsNotDisplayed();
onView(withId(R.id.textview)).perform(replaceText(text));
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
@@ -404,8 +395,6 @@
final String text = "abcd efg hijk lmn";
onView(withId(R.id.textview)).perform(replaceText(text));
- assertNoSelectionHandles();
-
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
onHandleView(com.android.internal.R.id.selection_start_handle)
@@ -428,8 +417,6 @@
final String text = "abc \u0621\u0622\u0623 def";
onView(withId(R.id.textview)).perform(replaceText(text));
- assertNoSelectionHandles();
-
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0622')));
onHandleView(com.android.internal.R.id.selection_start_handle)
@@ -491,6 +478,7 @@
onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn\nopqr"));
}
+ @Suppress // Consistently failing.
@Test
public void testSelectionHandles_multiLine_rtl() {
// Arabic text.
@@ -649,13 +637,11 @@
onView(withId(R.id.textview)).perform(replaceText(text));
final TextView textView = mActivity.findViewById(R.id.textview);
- assertFloatingToolbarIsNotDisplayed();
mActivityRule.runOnUiThread(
() -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
mInstrumentation.waitForIdleSync();
- sleepForFloatingToolbarPopup();
// Don't automatically start action mode.
- assertFloatingToolbarIsNotDisplayed();
+ // TODO: Implement assertActionModeNotStarted()
// Make sure that "Select All" is included in the selection action mode when the entire text
// is not selected.
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
@@ -685,8 +671,6 @@
() -> Selection.setSelection((Spannable) textView.getText(), 0));
mInstrumentation.waitForIdleSync();
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsNotDisplayed();
// Make sure that user click can trigger the insertion action mode.
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
diff --git a/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java b/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java
index 6a2233b..1693e54 100644
--- a/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java
+++ b/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java
@@ -36,6 +36,10 @@
private DragHandleUtils() {}
+ /**
+ * @deprecated Negative assertions are taking too long to timeout in Espresso.
+ */
+ @Deprecated
public static void assertNoSelectionHandles() {
try {
onView(isAssignableFrom(Editor.SelectionHandleView.class))
diff --git a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
index f7069b3..b6986d5 100644
--- a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
+++ b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
@@ -87,7 +87,9 @@
* Asserts that the floating toolbar is not displayed on screen.
*
* @throws AssertionError if the assertion fails
+ * @deprecated Negative assertions are taking too long to timeout in Espresso.
*/
+ @Deprecated
public static void assertFloatingToolbarIsNotDisplayed() {
try {
onFloatingToolBar().check(matches(isDisplayed()));
@@ -173,12 +175,31 @@
* @throws AssertionError if the assertion fails
*/
public static void assertFloatingToolbarDoesNotContainItem(String itemLabel) {
- try{
- assertFloatingToolbarContainsItem(itemLabel);
- } catch (AssertionError e) {
- return;
- }
- throw new AssertionError("Floating toolbar contains " + itemLabel);
+ onFloatingToolBar().check(matches(new TypeSafeMatcher<View>() {
+ @Override
+ public boolean matchesSafely(View view) {
+ return doesNotContainItem(view);
+ }
+
+ @Override
+ public void describeTo(Description description) {}
+
+ private boolean doesNotContainItem(View view) {
+ if (view.getTag() instanceof MenuItem) {
+ if (itemLabel.equals(((MenuItem) view.getTag()).getTitle().toString())) {
+ return false;
+ }
+ } else if (view instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) view;
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ if (doesNotContainItem(viewGroup.getChildAt(i))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }));
}
/**
diff --git a/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java b/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java
index 795e09c..06cb75d 100644
--- a/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java
+++ b/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java
@@ -54,6 +54,10 @@
mListView.setVerticalFadingEdgeEnabled(true);
mListView.setFadingEdgeLength(10);
ensureNotInTouchMode();
+
+ // focus the listview
+ mActivity.runOnUiThread(() -> mListView.requestFocus());
+ getInstrumentation().waitForIdleSync();
}
@Override
diff --git a/core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java b/core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java
index 8f62b2c..53eeb48 100644
--- a/core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java
+++ b/core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java
@@ -18,15 +18,13 @@
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase;
+import android.test.TouchUtils;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
-import android.test.TouchUtils;
import android.view.KeyEvent;
import android.widget.AbsListView;
import android.widget.GridView;
-import android.widget.gridview.GridScrollListener;
-
public class GridScrollListenerTest extends ActivityInstrumentationTestCase<GridScrollListener> implements
AbsListView.OnScrollListener {
private GridScrollListener mActivity;
@@ -52,53 +50,61 @@
public void testPreconditions() {
assertNotNull(mActivity);
assertNotNull(mGridView);
-
+
assertEquals(0, mFirstVisibleItem);
}
-
+
@LargeTest
public void testKeyScrolling() {
Instrumentation inst = getInstrumentation();
-
+ // focus the gridview
+ mActivity.runOnUiThread(() -> mGridView.requestFocus());
+ inst.waitForIdleSync();
+
int firstVisibleItem = mFirstVisibleItem;
for (int i = 0; i < mVisibleItemCount * 2; i++) {
inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
}
inst.waitForIdleSync();
-
+
assertTrue("Arrow scroll did not happen", mFirstVisibleItem > firstVisibleItem);
-
+
firstVisibleItem = mFirstVisibleItem;
- inst.sendCharacterSync(KeyEvent.KEYCODE_SPACE);
+ KeyEvent upDown = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_UP, 0, KeyEvent.META_ALT_ON);
+ KeyEvent upUp = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_DPAD_UP, 0, KeyEvent.META_ALT_ON);
+ inst.sendKeySync(upDown);
+ inst.sendKeySync(upUp);
inst.waitForIdleSync();
-
- assertTrue("Page scroll did not happen", mFirstVisibleItem > firstVisibleItem);
-
+
+ assertTrue("Page scroll did not happen", mFirstVisibleItem < firstVisibleItem);
+
firstVisibleItem = mFirstVisibleItem;
- KeyEvent down = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+ KeyEvent down = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
- KeyEvent up = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
+ KeyEvent up = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
inst.sendKeySync(down);
inst.sendKeySync(up);
inst.waitForIdleSync();
-
+
assertTrue("Full scroll did not happen", mFirstVisibleItem > firstVisibleItem);
- assertEquals("Full scroll did not happen", mTotalItemCount,
- mFirstVisibleItem + mVisibleItemCount);
+ assertEquals("Full scroll did not happen", mTotalItemCount,
+ mFirstVisibleItem + mVisibleItemCount);
}
@LargeTest
public void testTouchScrolling() {
Instrumentation inst = getInstrumentation();
-
+
int firstVisibleItem = mFirstVisibleItem;
TouchUtils.dragQuarterScreenUp(this);
TouchUtils.dragQuarterScreenUp(this);
assertTrue("Touch scroll did not happen", mFirstVisibleItem > firstVisibleItem);
}
-
+
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mFirstVisibleItem = firstVisibleItem;
mVisibleItemCount = visibleItemCount;
diff --git a/core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java b/core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java
index 2d6e75e..7b29a66 100644
--- a/core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java
+++ b/core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java
@@ -18,14 +18,13 @@
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase;
+import android.test.TouchUtils;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.view.KeyEvent;
import android.widget.AbsListView;
import android.widget.ListView;
-import android.test.TouchUtils;
-
public class ListScrollListenerTest extends ActivityInstrumentationTestCase<ListScrollListener> implements
AbsListView.OnScrollListener {
private ListScrollListener mActivity;
@@ -51,38 +50,46 @@
public void testPreconditions() {
assertNotNull(mActivity);
assertNotNull(mListView);
-
+
assertEquals(0, mFirstVisibleItem);
}
-
+
@LargeTest
public void testKeyScrolling() {
Instrumentation inst = getInstrumentation();
-
+ // focus the listview
+ mActivity.runOnUiThread(() -> mListView.requestFocus());
+ inst.waitForIdleSync();
+
int firstVisibleItem = mFirstVisibleItem;
for (int i = 0; i < mVisibleItemCount * 2; i++) {
inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
}
inst.waitForIdleSync();
assertTrue("Arrow scroll did not happen", mFirstVisibleItem > firstVisibleItem);
-
+
firstVisibleItem = mFirstVisibleItem;
- inst.sendCharacterSync(KeyEvent.KEYCODE_SPACE);
+ KeyEvent upDown = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_UP, 0, KeyEvent.META_ALT_ON);
+ KeyEvent upUp = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_DPAD_UP, 0, KeyEvent.META_ALT_ON);
+ inst.sendKeySync(upDown);
+ inst.sendKeySync(upUp);
inst.waitForIdleSync();
- assertTrue("Page scroll did not happen", mFirstVisibleItem > firstVisibleItem);
-
+ assertTrue("Page scroll did not happen", mFirstVisibleItem < firstVisibleItem);
+
firstVisibleItem = mFirstVisibleItem;
- KeyEvent down = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+ KeyEvent down = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
- KeyEvent up = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
+ KeyEvent up = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
inst.sendKeySync(down);
inst.sendKeySync(up);
inst.waitForIdleSync();
-
+
assertTrue("Full scroll did not happen", mFirstVisibleItem > firstVisibleItem);
- assertEquals("Full scroll did not happen", mTotalItemCount,
- mFirstVisibleItem + mVisibleItemCount);
+ assertEquals("Full scroll did not happen", mTotalItemCount,
+ mFirstVisibleItem + mVisibleItemCount);
}
@LargeTest
@@ -93,13 +100,13 @@
assertTrue("Touch scroll did not happen", mFirstVisibleItem > firstVisibleItem);
}
-
+
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mFirstVisibleItem = firstVisibleItem;
mVisibleItemCount = visibleItemCount;
mTotalItemCount = totalItemCount;
}
- public void onScrollStateChanged(AbsListView view, int scrollState) {
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
}
}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java
index 9b1cc0a..c191d71 100644
--- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java
@@ -50,6 +50,10 @@
@LargeTest
public void testScrollToBottom() {
+ // focus the listview
+ getActivity().runOnUiThread(() -> mListView.requestFocus());
+ getInstrumentation().waitForIdleSync();
+
final int numItems = mListView.getAdapter().getCount();
final int listBottom = mListView.getHeight() - mListView.getListPaddingBottom();
for (int i = 0; i < numItems; i++) {
@@ -78,6 +82,10 @@
@LargeTest
public void testScrollToTop() {
+ // focus the listview
+ getActivity().runOnUiThread(() -> mListView.requestFocus());
+ getInstrumentation().waitForIdleSync();
+
final int numItems = mListView.getAdapter().getCount();
for (int i = 0; i < numItems - 1; i++) {
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java
index 957be01..56ca009 100644
--- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java
@@ -16,13 +16,12 @@
package android.widget.listview.arrowscroll;
-import android.widget.listview.ListWithNoFadingEdge;
-
import android.test.ActivityInstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.ListView;
import android.view.KeyEvent;
+import android.widget.ListView;
+import android.widget.listview.ListWithNoFadingEdge;
public class ListWithNoFadingEdgeTest extends ActivityInstrumentationTestCase<ListWithNoFadingEdge> {
@@ -49,17 +48,21 @@
@MediumTest
public void testScrollDownToBottom() {
+ getActivity().runOnUiThread(() -> mListView.requestFocus());
+ getInstrumentation().waitForIdleSync();
final int numItems = mListView.getCount();
for (int i = 0; i < numItems; i++) {
assertEquals("selected position", i, mListView.getSelectedItemPosition());
sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
}
- assertEquals("selected position", numItems - 1, mListView.getSelectedItemPosition());
+ assertEquals("selected position", numItems - 1, mListView.getSelectedItemPosition());
}
@LargeTest
public void testScrollFromBottomToTop() {
+ getActivity().runOnUiThread(() -> mListView.requestFocus());
+ getInstrumentation().waitForIdleSync();
final int numItems = mListView.getCount();
getActivity().runOnUiThread(new Runnable() {
diff --git a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
index eb2a516..76aa93f 100644
--- a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
@@ -24,6 +24,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.test.TestLooper;
import android.test.suitebuilder.annotation.Suppress;
import com.android.internal.util.State;
@@ -343,6 +344,100 @@
}
/**
+ * Tests {@link StateMachine#quitNow()} immediately after {@link StateMachine#start()}.
+ */
+ class StateMachineQuitNowAfterStartTest extends StateMachine {
+ Collection<LogRec> mLogRecs;
+
+ StateMachineQuitNowAfterStartTest(String name, Looper looper) {
+ super(name, looper);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ @Override
+ public void onQuitting() {
+ tlog("onQuitting");
+ addLogRec(ON_QUITTING);
+ mLogRecs = mThisSm.copyLogRecs();
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ class S1 extends State {
+ @Override
+ public void enter() {
+ tlog("S1.enter");
+ addLogRec(ENTER);
+ }
+ @Override
+ public void exit() {
+ tlog("S1.exit");
+ addLogRec(EXIT);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ // Sleep and assume the other messages will be queued up.
+ case TEST_CMD_1: {
+ tlog("TEST_CMD_1");
+ sleep(500);
+ break;
+ }
+ default: {
+ tlog("default what=" + message.what);
+ break;
+ }
+ }
+ return HANDLED;
+ }
+ }
+
+ private StateMachineQuitNowAfterStartTest mThisSm;
+ private S1 mS1 = new S1();
+ }
+
+ /**
+ * When quitNow() is called immediately after start(), the QUIT_CMD gets processed
+ * before the INIT_CMD. This test ensures that the StateMachine can gracefully handle
+ * this sequencing of messages (QUIT before INIT).
+ */
+ @SmallTest
+ public void testStateMachineQuitNowAfterStart() throws Exception {
+ if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+ TestLooper testLooper = new TestLooper();
+ StateMachineQuitNowAfterStartTest smQuitNowAfterStartTest =
+ new StateMachineQuitNowAfterStartTest(
+ "smQuitNowAfterStartTest", testLooper.getLooper());
+ smQuitNowAfterStartTest.start();
+ smQuitNowAfterStartTest.quitNow();
+ if (smQuitNowAfterStartTest.isDbg()) tlog("testStateMachineQuitNowAfterStart E");
+
+ testLooper.dispatchAll();
+ dumpLogRecs(smQuitNowAfterStartTest.mLogRecs);
+ assertEquals(2, smQuitNowAfterStartTest.mLogRecs.size());
+
+ LogRec lr;
+ Iterator<LogRec> itr = smQuitNowAfterStartTest.mLogRecs.iterator();
+ lr = itr.next();
+ assertEquals(EXIT, lr.getInfo());
+ assertEquals(smQuitNowAfterStartTest.mS1, lr.getState());
+
+ lr = itr.next();
+ assertEquals(ON_QUITTING, lr.getInfo());
+
+ if (smQuitNowAfterStartTest.isDbg()) tlog("testStateMachineQuitNowAfterStart X");
+ }
+
+ /**
* Test enter/exit can use transitionTo
*/
class StateMachineEnterExitTransitionToTest extends StateMachine {
diff --git a/core/tests/webkit/Android.mk b/core/tests/webkit/Android.mk
new file mode 100644
index 0000000..cd0c3d25
--- /dev/null
+++ b/core/tests/webkit/Android.mk
@@ -0,0 +1,44 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+
+# Include all test java files.
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, unit_tests_src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test
+
+LOCAL_PACKAGE_NAME := WebViewLoadingTests
+LOCAL_CERTIFICATE := platform
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_REQUIRED_MODULES := \
+ WebViewLoadingOnDiskTestApk \
+ WebViewLoadingFromApkTestApk
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/tests/webkit/AndroidManifest.xml b/core/tests/webkit/AndroidManifest.xml
new file mode 100644
index 0000000..42accdf
--- /dev/null
+++ b/core/tests/webkit/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.webkit.tests"
+ android:sharedUserId="android.uid.system">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.webkit.tests"
+ android:label="Frameworks WebView Loader Tests" />
+
+</manifest>
diff --git a/core/tests/webkit/AndroidTest.xml b/core/tests/webkit/AndroidTest.xml
new file mode 100644
index 0000000..78cfa46
--- /dev/null
+++ b/core/tests/webkit/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Frameworks WebView Loading Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="WebViewLoadingTests.apk" />
+ <option name="test-file-name" value="WebViewLoadingOnDiskTestApk.apk" />
+ <option name="test-file-name" value="WebViewLoadingFromApkTestApk.apk" />
+ <option name="cleanup-apks" value="true" />
+ <option name="alt-dir" value="out" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.webkit.tests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/core/tests/webkit/apk_with_native_libs/Android.mk b/core/tests/webkit/apk_with_native_libs/Android.mk
new file mode 100644
index 0000000..7c6c36e
--- /dev/null
+++ b/core/tests/webkit/apk_with_native_libs/Android.mk
@@ -0,0 +1,66 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+MY_PATH := $(LOCAL_PATH)
+
+# Set shared variables
+MY_MODULE_TAGS := optional
+MY_JNI_SHARED_LIBRARIES := libwebviewtest_jni
+MY_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+MY_SRC_FILES := $(call all-java-files-under, src)
+MY_SDK_VERSION := system_current
+MY_PROGUARD_ENABLED := disabled
+MY_MULTILIB := both
+
+# Recurse down the file tree.
+include $(call all-subdir-makefiles)
+
+
+
+# Builds an apk containing native libraries that will be unzipped on the device.
+include $(CLEAR_VARS)
+
+LOCAL_PATH := $(MY_PATH)
+LOCAL_PACKAGE_NAME := WebViewLoadingOnDiskTestApk
+LOCAL_MANIFEST_FILE := ondisk/AndroidManifest.xml
+
+LOCAL_MODULE_TAGS := $(MY_MODULE_TAGS)
+LOCAL_JNI_SHARED_LIBRARIES := $(MY_JNI_SHARED_LIBRARIES)
+LOCAL_MODULE_PATH := $(MY_MODULE_PATH)
+LOCAL_SRC_FILES := $(MY_SRC_FILES)
+LOCAL_SDK_VERSION := $(MY_SDK_VERSION)
+LOCAL_PROGUARD_ENABLED := $(MY_PROGUARD_ENABLED)
+LOCAL_MULTILIB := $(MY_MULTILIB)
+
+include $(BUILD_PACKAGE)
+
+
+# Builds an apk containing uncompressed native libraries that have to be
+# accessed through the APK itself on the device.
+include $(CLEAR_VARS)
+
+LOCAL_PATH := $(MY_PATH)
+LOCAL_PACKAGE_NAME := WebViewLoadingFromApkTestApk
+LOCAL_MANIFEST_FILE := inapk/AndroidManifest.xml
+
+LOCAL_MODULE_TAGS := $(MY_MODULE_TAGS)
+LOCAL_JNI_SHARED_LIBRARIES := $(MY_JNI_SHARED_LIBRARIES)
+LOCAL_MODULE_PATH := $(MY_MODULE_PATH)
+LOCAL_SRC_FILES := $(MY_SRC_FILES)
+LOCAL_SDK_VERSION := $(MY_SDK_VERSION)
+LOCAL_PROGUARD_ENABLED := $(MY_PROGUARD_ENABLED)
+LOCAL_MULTILIB := $(MY_MULTILIB)
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml b/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml
new file mode 100644
index 0000000..868b238
--- /dev/null
+++ b/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.webviewloading_test_from_apk"
+ android:versionCode="1"
+ android:versionName="0.0.0.1">
+
+ <application android:label="WebView Loading Test APK"
+ android:multiArch="true"
+ android:extractNativeLibs="false">
+ <meta-data android:name="com.android.webview.WebViewLibrary"
+ android:value="libwebviewtest_jni.so" />
+ </application>
+</manifest>
diff --git a/libs/protoutil/Android.mk b/core/tests/webkit/apk_with_native_libs/jni/Android.mk
similarity index 61%
rename from libs/protoutil/Android.mk
rename to core/tests/webkit/apk_with_native_libs/jni/Android.mk
index 2a2b087..fd5b5eb 100644
--- a/libs/protoutil/Android.mk
+++ b/core/tests/webkit/apk_with_native_libs/jni/Android.mk
@@ -12,28 +12,21 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := libprotoutil
+LOCAL_MODULE := libwebviewtest_jni
-LOCAL_CFLAGS := \
- -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
+LOCAL_MODULE_TAGS := optional
-LOCAL_SHARED_LIBRARIES := \
- libcutils \
- liblog \
+LOCAL_SRC_FILES := WebViewTestJniOnLoad.cpp
-LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/include
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
-LOCAL_SRC_FILES := \
- src/EncodedBuffer.cpp \
- src/ProtoOutputStream.cpp \
- src/protobuf.cpp \
+LOCAL_SDK_VERSION := current
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_MULTILIB := both
include $(BUILD_SHARED_LIBRARY)
-
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp
similarity index 71%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp
index a987a16..9b0502f 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-package com.android.internal.app;
+#include <jni.h>
-import android.graphics.Bitmap;
-
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
+jint JNI_OnLoad(JavaVM *vm, void *reserved) {
+ return JNI_VERSION_1_4;
}
diff --git a/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml b/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml
new file mode 100644
index 0000000..ffffeb8
--- /dev/null
+++ b/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.webviewloading_test_on_disk"
+ android:versionCode="1"
+ android:versionName="0.0.0.1">
+
+ <application android:label="WebView Loading Test APK"
+ android:multiArch="true">
+ <meta-data android:name="com.android.webview.WebViewLibrary"
+ android:value="libwebviewtest_jni.so" />
+ </application>
+</manifest>
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/tests/webkit/apk_with_native_libs/src/com/google/android/xts/webview_list/WebViewLoadingTestClass.java
similarity index 71%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to core/tests/webkit/apk_with_native_libs/src/com/google/android/xts/webview_list/WebViewLoadingTestClass.java
index a987a16..0efa4b4 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/tests/webkit/apk_with_native_libs/src/com/google/android/xts/webview_list/WebViewLoadingTestClass.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.internal.app;
-import android.graphics.Bitmap;
+package com.android.webview.chromium;
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
+/**
+ * An empty class for testing purposes.
+ */
+public class WebViewLoadingTestClass {
}
diff --git a/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java b/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java
new file mode 100644
index 0000000..e2f2d37
--- /dev/null
+++ b/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link WebViewLibraryLoader}.
+ * Use the following command to run these tests:
+ * make WebViewLoadingTests \
+ * && adb install -r -d \
+ * ${ANDROID_PRODUCT_OUT}/data/app/WebViewLoadingTests/WebViewLoadingTests.apk \
+ * && adb shell am instrument -e class 'android.webkit.WebViewLibraryLoaderTest' -w \
+ * 'com.android.webkit.tests/android.support.test.runner.AndroidJUnitRunner'
+ */
+@RunWith(AndroidJUnit4.class)
+public final class WebViewLibraryLoaderTest {
+ private static final String WEBVIEW_LIBS_ON_DISK_TEST_APK =
+ "com.android.webviewloading_test_on_disk";
+ private static final String WEBVIEW_LIBS_IN_APK_TEST_APK =
+ "com.android.webviewloading_test_from_apk";
+ private static final String WEBVIEW_LOADING_TEST_NATIVE_LIB = "libwebviewtest_jni.so";
+
+ private PackageInfo webviewOnDiskPackageInfo;
+ private PackageInfo webviewFromApkPackageInfo;
+
+ @Before public void setUp() throws PackageManager.NameNotFoundException {
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+ webviewOnDiskPackageInfo =
+ pm.getPackageInfo(WEBVIEW_LIBS_ON_DISK_TEST_APK, PackageManager.GET_META_DATA);
+ webviewFromApkPackageInfo =
+ pm.getPackageInfo(WEBVIEW_LIBS_IN_APK_TEST_APK, PackageManager.GET_META_DATA);
+ }
+
+ private static boolean is64BitDevice() {
+ return Build.SUPPORTED_64_BIT_ABIS.length > 0;
+ }
+
+ // We test the getWebViewNativeLibraryDirectory method here because it handled several different
+ // cases/combinations and it seems unnecessary to create one test-apk for each such combination
+ // and arch.
+
+ /**
+ * Ensure we fetch the correct native library directories in the multi-arch case where
+ * the primary ABI is 64-bit.
+ */
+ @SmallTest
+ @Test public void testGetWebViewLibDirMultiArchPrimary64bit() {
+ final String nativeLib = "nativeLib";
+ final String secondaryNativeLib = "secondaryNativeLib";
+ PackageInfo packageInfo = new PackageInfo();
+ ApplicationInfo ai = new ApplicationInfoBuilder().
+ // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP
+ setPrimaryCpuAbi("arm64-v8a").
+ setNativeLibraryDir(nativeLib).
+ setSecondaryCpuAbi("armeabi").
+ setSecondaryNativeLibraryDir(secondaryNativeLib).
+ create();
+ packageInfo.applicationInfo = ai;
+ String actual32Lib =
+ WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, false /* is64bit */);
+ String actual64Lib =
+ WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, true /* is64bit */);
+ assertEquals(nativeLib, actual64Lib);
+ assertEquals(secondaryNativeLib, actual32Lib);
+ }
+
+ /**
+ * Ensure we fetch the correct native library directory in the 64-bit single-arch case.
+ */
+ @SmallTest
+ @Test public void testGetWebViewLibDirSingleArch64bit() {
+ final String nativeLib = "nativeLib";
+ PackageInfo packageInfo = new PackageInfo();
+ ApplicationInfo ai = new ApplicationInfoBuilder().
+ // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP
+ setPrimaryCpuAbi("arm64-v8a").
+ setNativeLibraryDir(nativeLib).
+ create();
+ packageInfo.applicationInfo = ai;
+ String actual64Lib =
+ WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, true /* is64bit */);
+ assertEquals(nativeLib, actual64Lib);
+ }
+
+ /**
+ * Ensure we fetch the correct native library directory in the 32-bit single-arch case.
+ */
+ @SmallTest
+ @Test public void testGetWebViewLibDirSingleArch32bit() {
+ final String nativeLib = "nativeLib";
+ PackageInfo packageInfo = new PackageInfo();
+ ApplicationInfo ai = new ApplicationInfoBuilder().
+ // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP
+ setPrimaryCpuAbi("armeabi-v7a").
+ setNativeLibraryDir(nativeLib).
+ create();
+ packageInfo.applicationInfo = ai;
+ String actual32Lib =
+ WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, false /* is64bit */);
+ assertEquals(nativeLib, actual32Lib);
+ }
+
+ /**
+ * Ensure we fetch the correct 32-bit library path from an APK with 32-bit and 64-bit
+ * libraries unzipped onto disk.
+ */
+ @MediumTest
+ @Test public void testGetWebViewLibraryPathOnDisk32Bit()
+ throws WebViewFactory.MissingWebViewPackageException {
+ WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib =
+ WebViewLibraryLoader.getWebViewNativeLibrary(
+ webviewOnDiskPackageInfo, false /* is64bit */);
+ String expectedLibaryDirectory = is64BitDevice() ?
+ webviewOnDiskPackageInfo.applicationInfo.secondaryNativeLibraryDir :
+ webviewOnDiskPackageInfo.applicationInfo.nativeLibraryDir;
+ String lib32Path = expectedLibaryDirectory + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB;
+ assertEquals("Fetched incorrect 32-bit path from WebView library.",
+ lib32Path, actualNativeLib.path);
+ }
+
+ /**
+ * Ensure we fetch the correct 64-bit library path from an APK with 32-bit and 64-bit
+ * libraries unzipped onto disk.
+ */
+ @MediumTest
+ @Test public void testGetWebViewLibraryPathOnDisk64Bit()
+ throws WebViewFactory.MissingWebViewPackageException {
+ // A 32-bit device will not unpack 64-bit libraries.
+ if (!is64BitDevice()) return;
+
+ WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib =
+ WebViewLibraryLoader.getWebViewNativeLibrary(
+ webviewOnDiskPackageInfo, true /* is64bit */);
+ String lib64Path = webviewOnDiskPackageInfo.applicationInfo.nativeLibraryDir
+ + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB;
+ assertEquals("Fetched incorrect 64-bit path from WebView library.",
+ lib64Path, actualNativeLib.path);
+ }
+
+ /**
+ * Check the size of the 32-bit library fetched from an APK with both 32-bit and 64-bit
+ * libraries unzipped onto disk.
+ */
+ @MediumTest
+ @Test public void testGetWebView32BitLibrarySizeOnDiskIsNonZero()
+ throws WebViewFactory.MissingWebViewPackageException {
+ WebViewLibraryLoader.WebViewNativeLibrary actual32BitNativeLib =
+ WebViewLibraryLoader.getWebViewNativeLibrary(
+ webviewOnDiskPackageInfo, false /* is64bit */);
+ assertTrue(actual32BitNativeLib.size > 0);
+ }
+
+ /**
+ * Check the size of the 64-bit library fetched from an APK with both 32-bit and 64-bit
+ * libraries unzipped onto disk.
+ */
+ @MediumTest
+ @Test public void testGetWebView64BitLibrarySizeOnDiskIsNonZero()
+ throws WebViewFactory.MissingWebViewPackageException {
+ // A 32-bit device will not unpack 64-bit libraries.
+ if (!is64BitDevice()) return;
+ WebViewLibraryLoader.WebViewNativeLibrary actual64BitNativeLib =
+ WebViewLibraryLoader.getWebViewNativeLibrary(
+ webviewOnDiskPackageInfo, true /* is64bit */);
+ assertTrue(actual64BitNativeLib.size > 0);
+ }
+
+ /**
+ * Ensure we fetch the correct 32-bit library path from an APK with both 32-bit and 64-bit
+ * libraries stored uncompressed in the APK.
+ */
+ @MediumTest
+ @Test public void testGetWebView32BitLibraryPathFromApk()
+ throws WebViewFactory.MissingWebViewPackageException, IOException {
+ WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib =
+ WebViewLibraryLoader.getWebViewNativeLibrary(
+ webviewFromApkPackageInfo, false /* is64bit */);
+ // The device might have ignored the app's request to not extract native libs, so first
+ // check whether the library paths match those of extracted libraries.
+ String expectedLibaryDirectory = is64BitDevice() ?
+ webviewFromApkPackageInfo.applicationInfo.secondaryNativeLibraryDir :
+ webviewFromApkPackageInfo.applicationInfo.nativeLibraryDir;
+ String lib32Path = expectedLibaryDirectory + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB;
+ if (lib32Path.equals(actualNativeLib.path)) {
+ // If the libraries were extracted to disk, ensure that they're actually there.
+ assertTrue("The given WebView library doesn't exist.",
+ new File(actualNativeLib.path).exists());
+ } else { // The libraries were not extracted to disk.
+ assertIsValidZipEntryPath(actualNativeLib.path,
+ webviewFromApkPackageInfo.applicationInfo.sourceDir);
+ }
+ }
+
+ /**
+ * Ensure we fetch the correct 32-bit library path from an APK with both 32-bit and 64-bit
+ * libraries stored uncompressed in the APK.
+ */
+ @MediumTest
+ @Test public void testGetWebView64BitLibraryPathFromApk()
+ throws WebViewFactory.MissingWebViewPackageException, IOException {
+ // A 32-bit device will not unpack 64-bit libraries.
+ if (!is64BitDevice()) return;
+
+ WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib =
+ WebViewLibraryLoader.getWebViewNativeLibrary(
+ webviewFromApkPackageInfo, true /* is64bit */);
+ assertIsValidZipEntryPath(actualNativeLib.path,
+ webviewFromApkPackageInfo.applicationInfo.sourceDir);
+ }
+
+ private static void assertIsValidZipEntryPath(String path, String zipFilePath)
+ throws IOException {
+ assertTrue("The path to a zip entry must start with the path to the zip file itself."
+ + "Expected zip path: " + zipFilePath + ", actual zip entry: " + path,
+ path.startsWith(zipFilePath + "!/"));
+ String[] pathSplit = path.split("!/");
+ assertEquals("A zip file path should have two parts, the zip path, and the zip entry path.",
+ 2, pathSplit.length);
+ ZipFile zipFile = new ZipFile(pathSplit[0]);
+ assertNotNull("Path doesn't point to a valid zip entry: " + path,
+ zipFile.getEntry(pathSplit[1]));
+ }
+
+
+ /**
+ * Check the size of the 32-bit library fetched from an APK with both 32-bit and 64-bit
+ * libraries stored uncompressed in the APK.
+ */
+ @MediumTest
+ @Test public void testGetWebView32BitLibrarySizeFromApkIsNonZero()
+ throws WebViewFactory.MissingWebViewPackageException {
+ WebViewLibraryLoader.WebViewNativeLibrary actual32BitNativeLib =
+ WebViewLibraryLoader.getWebViewNativeLibrary(
+ webviewFromApkPackageInfo, false /* is64bit */);
+ assertTrue(actual32BitNativeLib.size > 0);
+ }
+
+ /**
+ * Check the size of the 64-bit library fetched from an APK with both 32-bit and 64-bit
+ * libraries stored uncompressed in the APK.
+ */
+ @MediumTest
+ @Test public void testGetWebView64BitLibrarySizeFromApkIsNonZero()
+ throws WebViewFactory.MissingWebViewPackageException {
+ // A 32-bit device will not unpack 64-bit libraries.
+ if (!is64BitDevice()) return;
+
+ WebViewLibraryLoader.WebViewNativeLibrary actual64BitNativeLib =
+ WebViewLibraryLoader.getWebViewNativeLibrary(
+ webviewFromApkPackageInfo, true /* is64bit */);
+ assertTrue(actual64BitNativeLib.size > 0);
+ }
+
+ private static class ApplicationInfoBuilder {
+ ApplicationInfo ai;
+
+ public ApplicationInfoBuilder setPrimaryCpuAbi(String primaryCpuAbi) {
+ ai.primaryCpuAbi = primaryCpuAbi;
+ return this;
+ }
+
+ public ApplicationInfoBuilder setSecondaryCpuAbi(String secondaryCpuAbi) {
+ ai.secondaryCpuAbi = secondaryCpuAbi;
+ return this;
+ }
+
+ public ApplicationInfoBuilder setNativeLibraryDir(String nativeLibraryDir) {
+ ai.nativeLibraryDir = nativeLibraryDir;
+ return this;
+ }
+
+ public ApplicationInfoBuilder setSecondaryNativeLibraryDir(
+ String secondaryNativeLibraryDir) {
+ ai.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
+ return this;
+ }
+
+ public ApplicationInfoBuilder setMetaData(Bundle metaData) {
+ ai.metaData = metaData;
+ return this;
+ }
+
+ public ApplicationInfoBuilder() {
+ ai = new android.content.pm.ApplicationInfo();
+ }
+
+ public ApplicationInfo create() {
+ return ai;
+ }
+ }
+}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
new file mode 100644
index 0000000..f7a3e1a
--- /dev/null
+++ b/data/etc/OWNERS
@@ -0,0 +1,7 @@
+per-file privapp-permissions-platform.xml = bpoiesz@google.com
+per-file privapp-permissions-platform.xml = fkupolov@google.com
+per-file privapp-permissions-platform.xml = hackbod@android.com
+per-file privapp-permissions-platform.xml = jsharkey@android.com
+per-file privapp-permissions-platform.xml = svetoslavganov@google.com
+per-file privapp-permissions-platform.xml = toddke@google.com
+per-file privapp-permissions-platform.xml = yamasani@google.com
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 6f2b038..2f9ae57 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -24,6 +24,10 @@
<permission name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" />
</privapp-permissions>
+ <privapp-permissions package="com.android.apps.tag">
+ <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.backupconfirm">
<permission name="android.permission.BACKUP"/>
<permission name="android.permission.CRYPT_KEEPER"/>
@@ -54,6 +58,7 @@
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
<permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
+ <permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.STOP_APP_SWITCHES"/>
<permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
<permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/>
@@ -353,6 +358,7 @@
<permission name="android.permission.WRITE_DREAM_STATE"/>
<permission name="android.permission.WRITE_MEDIA_STORAGE"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission name="com.android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
</privapp-permissions>
<privapp-permissions package="com.android.tv">
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 9961ed6..3d65bd2 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -250,10 +250,10 @@
FontFamily fontFamily = new FontFamily();
for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
- // TODO: Add variation font support. (b/37853920)
if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
0 /* resourceCookie */, false /* isAsset */, fontFile.getTtcIndex(),
- fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) {
+ fontFile.getWeight(), fontFile.getItalic(),
+ FontVariationAxis.fromFontVariationSettings(fontFile.getVariationSettings()))) {
return null;
}
}
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 90d6ab8..e74dc6d 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -132,7 +132,7 @@
* <td>translateY</td>
* </tr>
* <tr>
- * <td rowspan="8"><path></td>
+ * <td rowspan="9"><path></td>
* <td>pathData</td>
* </tr>
* <tr>
@@ -154,6 +154,9 @@
* <td>trimPathStart</td>
* </tr>
* <tr>
+ * <td>trimPathEnd</td>
+ * </tr>
+ * <tr>
* <td>trimPathOffset</td>
* </tr>
* <tr>
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index ceac325..7b2e21a 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -213,12 +213,79 @@
* </vector>
* </pre>
* </li>
- * <li>And here is an example of linear gradient color, which is supported in SDK 24+.
+ * <h4>Gradient support</h4>
+ * We support 3 types of gradients: {@link android.graphics.LinearGradient},
+ * {@link android.graphics.RadialGradient}, or {@link android.graphics.SweepGradient}.
+ * <p/>
+ * And we support all of 3 types of tile modes {@link android.graphics.Shader.TileMode}:
+ * CLAMP, REPEAT, MIRROR.
+ * <p/>
+ * All of the attributes are listed in {@link android.R.styleable#GradientColor}.
+ * Note that different attributes are relevant for different types of gradient.
+ * <table border="2" align="center" cellpadding="5">
+ * <thead>
+ * <tr>
+ * <th>LinearGradient</th>
+ * <th>RadialGradient</th>
+ * <th>SweepGradient</th>
+ * </tr>
+ * </thead>
+ * <tr>
+ * <td>startColor </td>
+ * <td>startColor</td>
+ * <td>startColor</td>
+ * </tr>
+ * <tr>
+ * <td>centerColor</td>
+ * <td>centerColor</td>
+ * <td>centerColor</td>
+ * </tr>
+ * <tr>
+ * <td>endColor</td>
+ * <td>endColor</td>
+ * <td>endColor</td>
+ * </tr>
+ * <tr>
+ * <td>type</td>
+ * <td>type</td>
+ * <td>type</td>
+ * </tr>
+ * <tr>
+ * <td>tileMode</td>
+ * <td>tileMode</td>
+ * <td>tileMode</td>
+ * </tr>
+ * <tr>
+ * <td>startX</td>
+ * <td>centerX</td>
+ * <td>centerX</td>
+ * </tr>
+ * <tr>
+ * <td>startY</td>
+ * <td>centerY</td>
+ * <td>centerY</td>
+ * </tr>
+ * <tr>
+ * <td>endX</td>
+ * <td>gradientRadius</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>endY</td>
+ * <td></td>
+ * <td></td>
+ * </tr>
+ * </table>
+ * <p/>
+ * Also note that if any color item {@link android.R.styleable#GradientColorItem} is defined, then
+ * startColor, centerColor and endColor will be ignored.
+ * <p/>
* See more details in {@link android.R.styleable#GradientColor} and
* {@link android.R.styleable#GradientColorItem}.
+ * <p/>
+ * Here is a simple example that defines a linear gradient.
* <pre>
* <gradient xmlns:android="http://schemas.android.com/apk/res/android"
- * android:angle="90"
* android:startColor="?android:attr/colorPrimary"
* android:endColor="?android:attr/colorControlActivated"
* android:centerColor="#f00"
@@ -229,7 +296,18 @@
* android:type="linear">
* </gradient>
* </pre>
- * </li>
+ * And here is a simple example that defines a radial gradient using color items.
+ * <pre>
+ * <gradient xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:centerX="300"
+ * android:centerY="300"
+ * android:gradientRadius="100"
+ * android:type="radial">
+ * <item android:offset="0.1" android:color="#0ff"/>
+ * <item android:offset="0.4" android:color="#fff"/>
+ * <item android:offset="0.9" android:color="#ff0"/>
+ * </gradient>
+ * </pre>
*
*/
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index 0e6b31e..bec22c9 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -93,6 +93,8 @@
LOCAL_MODULE := legacy.test.stubs
LOCAL_SOURCE_FILES_ALL_GENERATED := true
+LOCAL_SDK_VERSION := current
+LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp)
# Make sure to run droiddoc first to generate the stub source files.
LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp)
@@ -162,3 +164,5 @@
include $(BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY)
endif # HOST_OS == linux
+
+legacy_test_api_gen_stamp :=
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 5603508..3c8736e 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -150,6 +150,14 @@
ALOGI("Destroying AssetManager in %p #%d\n", this, count);
}
+ // Manually close any fd paths for which we have not yet opened their zip (which
+ // will take ownership of the fd and close it when done).
+ for (size_t i=0; i<mAssetPaths.size(); i++) {
+ if (mAssetPaths[i].rawFd >= 0 && mAssetPaths[i].zip == NULL) {
+ close(mAssetPaths[i].rawFd);
+ }
+ }
+
delete mConfig;
delete mResources;
@@ -280,7 +288,35 @@
}
return true;
- }
+}
+
+bool AssetManager::addAssetFd(
+ int fd, const String8& debugPathName, int32_t* cookie, bool appAsLib,
+ bool assume_ownership) {
+ AutoMutex _l(mLock);
+
+ asset_path ap;
+
+ ap.path = debugPathName;
+ ap.rawFd = fd;
+ ap.type = kFileTypeRegular;
+ ap.assumeOwnership = assume_ownership;
+
+ ALOGV("In %p Asset fd %d name: %s", this, fd, ap.path.string());
+
+ mAssetPaths.add(ap);
+
+ // new paths are always added at the end
+ if (cookie) {
+ *cookie = static_cast<int32_t>(mAssetPaths.size());
+ }
+
+ if (mResources != NULL) {
+ appendPathToResTable(ap, appAsLib);
+ }
+
+ return true;
+}
bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApkPath,
uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, size_t* outSize)
@@ -505,7 +541,7 @@
Asset* idmap = openIdmapLocked(ap);
size_t nextEntryIdx = mResources->getTableCount();
ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
- if (ap.type != kFileTypeDirectory) {
+ if (ap.type != kFileTypeDirectory && ap.rawFd < 0) {
if (nextEntryIdx == 0) {
// The first item is typically the framework resources,
// which we want to avoid parsing every time.
@@ -738,6 +774,8 @@
{
Asset* pAsset = NULL;
+ ALOGV("openNonAssetInPath: name=%s type=%d fd=%d", fileName, ap.type, ap.rawFd);
+
/* look at the filesystem on disk */
if (ap.type == kFileTypeDirectory) {
String8 path(ap.path);
@@ -752,7 +790,7 @@
}
if (pAsset != NULL) {
- //printf("FOUND NA '%s' on disk\n", fileName);
+ ALOGV("FOUND NA '%s' on disk", fileName);
pAsset->setAssetSource(path);
}
@@ -763,10 +801,10 @@
/* check the appropriate Zip file */
ZipFileRO* pZip = getZipFileLocked(ap);
if (pZip != NULL) {
- //printf("GOT zip, checking NA '%s'\n", (const char*) path);
+ ALOGV("GOT zip, checking NA '%s'", (const char*) path);
ZipEntryRO entry = pZip->findEntryByName(path.string());
if (entry != NULL) {
- //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
+ ALOGV("FOUND NA in Zip file for %s", (const char*) path);
pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
pZip->releaseEntry(entry);
}
@@ -817,7 +855,17 @@
{
ALOGV("getZipFileLocked() in %p\n", this);
- return mZipSet.getZip(ap.path);
+ if (ap.zip != NULL) {
+ return ap.zip->getZip();
+ }
+
+ if (ap.rawFd < 0) {
+ ap.zip = mZipSet.getSharedZip(ap.path);
+ } else {
+ ap.zip = SharedZip::create(ap.rawFd, ap.path);
+
+ }
+ return ap.zip != NULL ? ap.zip->getZip() : NULL;
}
/*
@@ -1374,6 +1422,21 @@
}
}
+AssetManager::SharedZip::SharedZip(int fd, const String8& path)
+ : mPath(path), mZipFile(NULL), mModWhen(0),
+ mResourceTableAsset(NULL), mResourceTable(NULL)
+{
+ if (kIsDebug) {
+ ALOGI("Creating SharedZip %p fd=%d %s\n", this, fd, (const char*)mPath);
+ }
+ ALOGV("+++ opening zip fd=%d '%s'\n", fd, mPath.string());
+ mZipFile = ZipFileRO::openFd(fd, mPath.string());
+ if (mZipFile == NULL) {
+ ::close(fd);
+ ALOGD("failed to open Zip archive fd=%d '%s'\n", fd, mPath.string());
+ }
+}
+
sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path,
bool createIfNotPresent)
{
@@ -1389,7 +1452,11 @@
zip = new SharedZip(path, modWhen);
gOpen.add(path, zip);
return zip;
+}
+sp<AssetManager::SharedZip> AssetManager::SharedZip::create(int fd, const String8& path)
+{
+ return new SharedZip(fd, path);
}
ZipFileRO* AssetManager::SharedZip::getZip()
@@ -1500,19 +1567,23 @@
mZipFile.editItemAt(idx) = NULL;
}
-
/*
* Retrieve the appropriate Zip file from the set.
*/
ZipFileRO* AssetManager::ZipSet::getZip(const String8& path)
{
+ return getSharedZip(path)->getZip();
+}
+
+const sp<AssetManager::SharedZip> AssetManager::ZipSet::getSharedZip(const String8& path)
+{
int idx = getIndex(path);
sp<SharedZip> zip = mZipFile[idx];
if (zip == NULL) {
zip = SharedZip::get(path);
mZipFile.editItemAt(idx) = zip;
}
- return zip->getZip();
+ return zip;
}
Asset* AssetManager::ZipSet::getZipResourceTableAsset(const String8& path)
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 87999c3..17de8fa 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2847,14 +2847,111 @@
}
memcpy(str + charsWritten, localeVariant, sizeof(localeVariant));
}
+
+ /* TODO: Add BCP47 extension. It requires RESTABLE_MAX_LOCALE_LEN
+ * increase from 28 to 42 bytes (-u-nu-xxxxxxxx) */
}
-/* static */ inline bool assignLocaleComponent(ResTable_config* config,
- const char* start, size_t size) {
+struct LocaleParserState {
+ enum State : uint8_t {
+ BASE, UNICODE_EXTENSION, IGNORE_THE_REST
+ } parserState;
+ enum UnicodeState : uint8_t {
+ /* Initial state after the Unicode singleton is detected. Either a keyword
+ * or an attribute is expected. */
+ NO_KEY,
+ /* Unicode extension key (but not attribute) is expected. Next states:
+ * NO_KEY, IGNORE_KEY or NUMBERING_SYSTEM. */
+ EXPECT_KEY,
+ /* A key is detected, however it is not supported for now. Ignore its
+ * value. Next states: IGNORE_KEY or NUMBERING_SYSTEM. */
+ IGNORE_KEY,
+ /* Numbering system key was detected. Store its value in the configuration
+ * localeNumberingSystem field. Next state: EXPECT_KEY */
+ NUMBERING_SYSTEM
+ } unicodeState;
+
+ LocaleParserState(): parserState(BASE), unicodeState(NO_KEY) {}
+};
+
+/* static */ inline LocaleParserState assignLocaleComponent(ResTable_config* config,
+ const char* start, size_t size, LocaleParserState state) {
+
+ /* It is assumed that this function is not invoked with state.parserState
+ * set to IGNORE_THE_REST. The condition is checked by setBcp47Locale
+ * function. */
+
+ if (state.parserState == LocaleParserState::UNICODE_EXTENSION) {
+ switch (size) {
+ case 1:
+ /* Other BCP 47 extensions are not supported at the moment */
+ state.parserState = LocaleParserState::IGNORE_THE_REST;
+ break;
+ case 2:
+ if (state.unicodeState == LocaleParserState::NO_KEY ||
+ state.unicodeState == LocaleParserState::EXPECT_KEY) {
+ /* Analyze Unicode extension key. Currently only 'nu'
+ * (numbering system) is supported.*/
+ if ((start[0] == 'n' || start[0] == 'N') &&
+ (start[1] == 'u' || start[1] == 'U')) {
+ state.unicodeState = LocaleParserState::NUMBERING_SYSTEM;
+ } else {
+ state.unicodeState = LocaleParserState::IGNORE_KEY;
+ }
+ } else {
+ /* Keys are not allowed in other state allowed, ignore the rest. */
+ state.parserState = LocaleParserState::IGNORE_THE_REST;
+ }
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ switch (state.unicodeState) {
+ case LocaleParserState::NUMBERING_SYSTEM:
+ /* Accept only the first occurrence of the numbering system. */
+ if (config->localeNumberingSystem[0] == '\0') {
+ for (size_t i = 0; i < size; ++i) {
+ config->localeNumberingSystem[i] = tolower(start[i]);
+ }
+ state.unicodeState = LocaleParserState::EXPECT_KEY;
+ } else {
+ state.parserState = LocaleParserState::IGNORE_THE_REST;
+ }
+ break;
+ case LocaleParserState::IGNORE_KEY:
+ /* Unsupported Unicode keyword. Ignore. */
+ state.unicodeState = LocaleParserState::EXPECT_KEY;
+ break;
+ case LocaleParserState::EXPECT_KEY:
+ /* A keyword followed by an attribute is not allowed. */
+ state.parserState = LocaleParserState::IGNORE_THE_REST;
+ break;
+ case LocaleParserState::NO_KEY:
+ /* Extension attribute. Do nothing. */
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ /* Unexpected field length - ignore the rest and treat as an error */
+ state.parserState = LocaleParserState::IGNORE_THE_REST;
+ }
+ return state;
+ }
switch (size) {
case 0:
- return false;
+ state.parserState = LocaleParserState::IGNORE_THE_REST;
+ break;
+ case 1:
+ state.parserState = (start[0] == 'u' || start[0] == 'U')
+ ? LocaleParserState::UNICODE_EXTENSION
+ : LocaleParserState::IGNORE_THE_REST;
+ break;
case 2:
case 3:
config->language[0] ? config->packRegion(start) : config->packLanguage(start);
@@ -2878,30 +2975,35 @@
}
break;
default:
- return false;
+ state.parserState = LocaleParserState::IGNORE_THE_REST;
}
- return true;
+ return state;
}
void ResTable_config::setBcp47Locale(const char* in) {
locale = 0;
memset(localeScript, 0, sizeof(localeScript));
memset(localeVariant, 0, sizeof(localeVariant));
+ memset(localeNumberingSystem, 0, sizeof(localeNumberingSystem));
- const char* separator = in;
const char* start = in;
- while ((separator = strchr(start, '-')) != NULL) {
+ LocaleParserState state;
+ while (const char* separator = strchr(start, '-')) {
const size_t size = separator - start;
- if (!assignLocaleComponent(this, start, size)) {
- fprintf(stderr, "Invalid BCP-47 locale string: %s", in);
+ state = assignLocaleComponent(this, start, size, state);
+ if (state.parserState == LocaleParserState::IGNORE_THE_REST) {
+ fprintf(stderr, "Invalid BCP-47 locale string: %s\n", in);
+ break;
}
-
start = (separator + 1);
}
- const size_t size = in + strlen(in) - start;
- assignLocaleComponent(this, start, size);
+ if (state.parserState != LocaleParserState::IGNORE_THE_REST) {
+ const size_t size = strlen(start);
+ assignLocaleComponent(this, start, size, state);
+ }
+
localeScriptWasComputed = (localeScript[0] == '\0');
if (localeScriptWasComputed) {
computeScript();
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index 49fe8a2..6e2ca60c 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -55,7 +55,9 @@
ZipFileRO::~ZipFileRO() {
CloseArchive(mHandle);
- free(mFileName);
+ if (mFileName != NULL) {
+ free(mFileName);
+ }
}
/*
@@ -76,6 +78,20 @@
}
+/* static */ ZipFileRO* ZipFileRO::openFd(int fd, const char* debugFileName,
+ bool assume_ownership)
+{
+ ZipArchiveHandle handle;
+ const int32_t error = OpenArchiveFd(fd, debugFileName, &handle, assume_ownership);
+ if (error) {
+ ALOGW("Error opening archive fd %d %s: %s", fd, debugFileName, ErrorCodeString(error));
+ CloseArchive(handle);
+ return NULL;
+ }
+
+ return new ZipFileRO(handle, strdup(debugFileName));
+}
+
ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const
{
_ZipEntryRO* data = new _ZipEntryRO;
@@ -139,7 +155,8 @@
prefix ? &pe : NULL,
suffix ? &se : NULL);
if (error) {
- ALOGW("Could not start iteration over %s: %s", mFileName, ErrorCodeString(error));
+ ALOGW("Could not start iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
+ ErrorCodeString(error));
delete ze;
return false;
}
@@ -154,7 +171,8 @@
int32_t error = Next(ze->cookie, &(ze->entry), &(ze->name));
if (error) {
if (error != -1) {
- ALOGW("Error iteration over %s: %s", mFileName, ErrorCodeString(error));
+ ALOGW("Error iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
+ ErrorCodeString(error));
}
return NULL;
}
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index 0441b9d..4254614 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -92,6 +92,20 @@
bool addOverlayPath(const String8& path, int32_t* cookie);
/*
+ * Add a new source for assets from an already open file descriptor.
+ * This does not give full AssetManager functionality for these assets,
+ * since the origin of the file is not known for purposes of sharing,
+ * overlay resolution, and other features. However it does allow you
+ * to do simple access to the contents of the given fd as an apk file.
+ *
+ * Returns "true" on success, "false" on failure. If 'cookie' is non-NULL,
+ * then on success, *cookie is set to the value corresponding to the
+ * newly-added asset source.
+ */
+ bool addAssetFd(int fd, const String8& debugPathName, int32_t* cookie,
+ bool appAsLib=false, bool assume_ownership=true);
+
+ /*
* Convenience for adding the standard system assets. Uses the
* ANDROID_ROOT environment variable to find them.
*/
@@ -195,15 +209,20 @@
uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, size_t* outSize);
private:
+ class SharedZip;
+
struct asset_path
{
- asset_path() : path(""), type(kFileTypeRegular), idmap(""),
- isSystemOverlay(false), isSystemAsset(false) {}
+ asset_path() : path(""), rawFd(-1), type(kFileTypeRegular), idmap(""),
+ isSystemOverlay(false), isSystemAsset(false), assumeOwnership(false) {}
String8 path;
+ int rawFd;
FileType type;
String8 idmap;
bool isSystemOverlay;
bool isSystemAsset;
+ bool assumeOwnership;
+ mutable sp<SharedZip> zip;
};
Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
@@ -238,6 +257,7 @@
class SharedZip : public RefBase {
public:
static sp<SharedZip> get(const String8& path, bool createIfNotPresent = true);
+ static sp<SharedZip> create(int fd, const String8& path);
ZipFileRO* getZip();
@@ -257,6 +277,7 @@
private:
SharedZip(const String8& path, time_t modWhen);
+ SharedZip(int fd, const String8& path);
SharedZip(); // <-- not implemented
String8 mPath;
@@ -290,6 +311,8 @@
*/
ZipFileRO* getZip(const String8& path);
+ const sp<SharedZip> getSharedZip(const String8& path);
+
Asset* getZipResourceTableAsset(const String8& path);
Asset* setZipResourceTableAsset(const String8& path, Asset* asset);
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 8547955..20d0178 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1182,6 +1182,10 @@
// tried but could not compute a script.
bool localeScriptWasComputed;
+ // The value of BCP 47 Unicode extension for key 'nu' (numbering system).
+ // Varies in length from 3 to 8 chars. Zero-filled value.
+ char localeNumberingSystem[8];
+
void copyFromDeviceNoSwap(const ResTable_config& o);
void copyFromDtoH(const ResTable_config& o);
@@ -1259,9 +1263,9 @@
// variants, it will be a modified bcp47 tag: b+en+Latn+US.
void appendDirLocale(String8& str) const;
- // Sets the values of language, region, script and variant to the
- // well formed BCP-47 locale contained in |in|. The input locale is
- // assumed to be valid and no validation is performed.
+ // Sets the values of language, region, script, variant and numbering
+ // system to the well formed BCP 47 locale contained in |in|.
+ // The input locale is assumed to be valid and no validation is performed.
void setBcp47Locale(const char* in);
inline void clearLocale() {
@@ -1269,6 +1273,7 @@
localeScriptWasComputed = false;
memset(localeScript, 0, sizeof(localeScript));
memset(localeVariant, 0, sizeof(localeVariant));
+ memset(localeNumberingSystem, 0, sizeof(localeNumberingSystem));
}
inline void computeScript() {
diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h
index 7680342..03154d0 100644
--- a/libs/androidfw/include/androidfw/ZipFileRO.h
+++ b/libs/androidfw/include/androidfw/ZipFileRO.h
@@ -80,6 +80,12 @@
static ZipFileRO* open(const char* zipFileName);
/*
+ * Open an archive from an already open file descriptor.
+ */
+ static ZipFileRO* openFd(int fd, const char* debugFileName,
+ bool assume_ownership = true);
+
+ /*
* Find an entry, by name. Returns the entry identifier, or NULL if
* not found.
*/
diff --git a/libs/androidfw/tests/ConfigLocale_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp
index 86a627e..35007c8 100644
--- a/libs/androidfw/tests/ConfigLocale_test.cpp
+++ b/libs/androidfw/tests/ConfigLocale_test.cpp
@@ -185,6 +185,7 @@
EXPECT_TRUE(test.localeScriptWasComputed);
EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
EXPECT_EQ(0, test.localeVariant[0]);
+ EXPECT_EQ(0, test.localeNumberingSystem[0]);
test.setBcp47Locale("eng-419");
char out[4] = {1, 1, 1, 1};
@@ -198,6 +199,7 @@
EXPECT_EQ('4', out[0]);
EXPECT_EQ('1', out[1]);
EXPECT_EQ('9', out[2]);
+ EXPECT_EQ(0, test.localeNumberingSystem[0]);
test.setBcp47Locale("en-Latn-419");
EXPECT_EQ('e', test.language[0]);
@@ -209,6 +211,7 @@
EXPECT_EQ('4', out[0]);
EXPECT_EQ('1', out[1]);
EXPECT_EQ('9', out[2]);
+ EXPECT_EQ(0, test.localeNumberingSystem[0]);
test.setBcp47Locale("de-1901");
memset(out, 1, 4);
@@ -222,6 +225,7 @@
test.unpackRegion(out);
EXPECT_EQ('\0', out[0]);
EXPECT_EQ(0, strcmp("1901", test.localeVariant));
+ EXPECT_EQ(0, test.localeNumberingSystem[0]);
test.setBcp47Locale("de-Latn-1901");
memset(out, 1, 4);
@@ -235,6 +239,44 @@
test.unpackRegion(out);
EXPECT_EQ('\0', out[0]);
EXPECT_EQ(0, strcmp("1901", test.localeVariant));
+ EXPECT_EQ(0, test.localeNumberingSystem[0]);
+
+ test.setBcp47Locale("ar-EG-u-nu-latn");
+ EXPECT_EQ('a', test.language[0]);
+ EXPECT_EQ('r', test.language[1]);
+ EXPECT_EQ('E', test.country[0]);
+ EXPECT_EQ('G', test.country[1]);
+ EXPECT_TRUE(test.localeScriptWasComputed);
+ EXPECT_EQ(0, memcmp("Arab", test.localeScript, 4));
+ EXPECT_EQ(0, test.localeVariant[0]);
+ EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4));
+
+ test.setBcp47Locale("ar-EG-u");
+ EXPECT_EQ(0, test.localeNumberingSystem[0]);
+
+ test.setBcp47Locale("ar-EG-u-nu");
+ EXPECT_EQ(0, test.localeNumberingSystem[0]);
+
+ test.setBcp47Locale("ar-EG-u-attr-nu-latn");
+ EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4));
+
+ test.setBcp47Locale("ar-EG-u-ca-gregory-nu-latn");
+ EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4));
+
+ test.setBcp47Locale("ar-EG-u-nu-latn-ca-gregory");
+ EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4));
+
+ test.setBcp47Locale("ar-EG-u-nu-toolongnumsys");
+ EXPECT_EQ(0, test.localeNumberingSystem[0]);
+
+ test.setBcp47Locale("ar-EG-u-nu-latn-nu-arab");
+ EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4));
+
+ test.setBcp47Locale("ar-EG-u-co-nu-latn");
+ EXPECT_EQ(0, test.localeNumberingSystem[0]);
+
+ test.setBcp47Locale("ar-u-co-abcd-attr-nu-latn");
+ EXPECT_EQ(0, test.localeNumberingSystem[0]);
}
TEST(ConfigLocaleTest, computeScript) {
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 40aecac..2644292 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -7,6 +7,8 @@
//"hwui_compile_for_perf",
],
+ cpp_std: "c++17",
+
cflags: [
"-DEGL_EGLEXT_PROTOTYPES",
"-DGL_GLEXT_PROTOTYPES",
@@ -354,7 +356,8 @@
"tests/unit/TestUtilsTests.cpp",
"tests/unit/TextDropShadowCacheTests.cpp",
"tests/unit/TextureCacheTests.cpp",
- "tests/unit/TypefaceTests.cpp",
+ "tests/unit/ThreadBaseTests.cpp",
+ "tests/unit/TypefaceTests.cpp",
"tests/unit/VectorDrawableTests.cpp",
"tests/unit/VectorDrawableAtlasTests.cpp",
],
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 6b8006c..0919e82 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -31,10 +31,11 @@
namespace uirenderer {
Extensions::Extensions() {
- if (Properties::getRenderPipelineType() != RenderPipelineType::OpenGL) {
- //Extensions class is used only by OpenGL pipeline
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ //Extensions class is used only by OpenGL and SkiaGL pipelines
//The code below will crash for SkiaVulkan, because OpenGL is not initialized
//TODO: instantiate Extensions class only for OpenGL pipeline
+ //TODO: remove the only usage of Extensions by SkiaGL in SkiaOpenGLReadback::copyImageInto
return;
}
const char* version = (const char*) glGetString(GL_VERSION);
@@ -65,6 +66,7 @@
mHas1BitStencil = extensions.has("GL_OES_stencil1");
mHas4BitStencil = extensions.has("GL_OES_stencil4");
mHasUnpackSubImage = extensions.has("GL_EXT_unpack_subimage");
+ mHasRenderableFloatTexture = extensions.has("GL_OES_texture_half_float");
mHasSRGB = mVersionMajor >= 3 || extensions.has("GL_EXT_sRGB");
mHasSRGBWriteControl = extensions.has("GL_EXT_sRGB_write_control");
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 0ecfdb1..7af7f79 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -38,6 +38,9 @@
inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; }
inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; }
inline bool hasFloatTextures() const { return mVersionMajor >= 3; }
+ inline bool hasRenderableFloatTextures() const {
+ return (mVersionMajor >= 3 && mVersionMinor >= 2) || mHasRenderableFloatTexture;
+ }
inline bool hasSRGB() const { return mHasSRGB; }
inline bool hasSRGBWriteControl() const { return hasSRGB() && mHasSRGBWriteControl; }
inline bool hasLinearBlending() const { return hasSRGB() && mHasLinearBlending; }
@@ -56,6 +59,7 @@
bool mHasSRGB;
bool mHasSRGBWriteControl;
bool mHasLinearBlending;
+ bool mHasRenderableFloatTexture;
int mVersionMajor;
int mVersionMinor;
diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp
index 2687410..751e2037 100644
--- a/libs/hwui/OpenGLReadback.cpp
+++ b/libs/hwui/OpenGLReadback.cpp
@@ -128,7 +128,8 @@
return CopyResult::DestinationInvalid;
}
- if (bitmap->colorType() == kRGBA_F16_SkColorType && !caches.extensions().hasFloatTextures()) {
+ if (bitmap->colorType() == kRGBA_F16_SkColorType &&
+ !caches.extensions().hasRenderableFloatTextures()) {
ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
return CopyResult::DestinationInvalid;
}
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/libs/hwui/SwapBehavior.h
similarity index 60%
copy from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
copy to libs/hwui/SwapBehavior.h
index a987a16..4091670c 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/libs/hwui/SwapBehavior.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,20 @@
* limitations under the License.
*/
-package com.android.internal.app;
+#ifndef HWUI_SWAPBEHAVIOR_H
+#define HWUI_SWAPBEHAVIOR_H
-import android.graphics.Bitmap;
+namespace android {
+namespace uirenderer {
+namespace renderthread {
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
- void send(in Bitmap screenshot);
-}
+enum class SwapBehavior {
+ kSwap_default,
+ kSwap_discardBuffer,
+};
+
+} // namespace renderthread
+} //namespace uirenderer
+} // namespace android
+
+#endif //HWUI_SWAPBEHAVIOR_H
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index a087035..6f05799 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -158,7 +158,8 @@
};
void Canvas::drawText(const uint16_t* text, int start, int count, int contextCount,
- float x, float y, int bidiFlags, const Paint& origPaint, const Typeface* typeface) {
+ float x, float y, minikin::Bidi bidiFlags, const Paint& origPaint,
+ const Typeface* typeface) {
// minikin may modify the original paint
Paint paint(origPaint);
@@ -206,8 +207,9 @@
const SkPath& path;
};
-void Canvas::drawTextOnPath(const uint16_t* text, int count, int bidiFlags, const SkPath& path,
- float hOffset, float vOffset, const Paint& paint, const Typeface* typeface) {
+void Canvas::drawTextOnPath(const uint16_t* text, int count, minikin::Bidi bidiFlags,
+ const SkPath& path, float hOffset, float vOffset, const Paint& paint,
+ const Typeface* typeface) {
Paint paintCopy(paint);
minikin::Layout layout = MinikinUtils::doLayout(
&paintCopy, bidiFlags, typeface, text, 0, count, count);
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index fbd8960..0a1bae7 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -33,6 +33,7 @@
namespace minikin {
class Layout;
+ enum class Bidi : uint8_t;
}
namespace android {
@@ -255,10 +256,12 @@
* and delegating the final draw to virtual drawGlyphs method.
*/
void drawText(const uint16_t* text, int start, int count, int contextCount,
- float x, float y, int bidiFlags, const Paint& origPaint, const Typeface* typeface);
+ float x, float y, minikin::Bidi bidiFlags, const Paint& origPaint,
+ const Typeface* typeface);
- void drawTextOnPath(const uint16_t* text, int count, int bidiFlags, const SkPath& path,
- float hOffset, float vOffset, const Paint& paint, const Typeface* typeface);
+ void drawTextOnPath(const uint16_t* text, int count, minikin::Bidi bidiFlags,
+ const SkPath& path, float hOffset, float vOffset, const Paint& paint,
+ const Typeface* typeface);
protected:
void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 7da7f38..814d2cf 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -51,9 +51,8 @@
return minikinStyle;
}
-minikin::Layout MinikinUtils::doLayout(const Paint* paint, int bidiFlags,
- const Typeface* typeface, const uint16_t* buf, size_t start, size_t count,
- size_t bufSize) {
+minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFlags,
+ const Typeface* typeface, const uint16_t* buf, size_t start, size_t count, size_t bufSize) {
minikin::MinikinPaint minikinPaint;
minikin::FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, paint, typeface);
minikin::Layout layout;
@@ -62,8 +61,9 @@
return layout;
}
-float MinikinUtils::measureText(const Paint* paint, int bidiFlags, const Typeface* typeface,
- const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances) {
+float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
+ const Typeface* typeface, const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ float *advances) {
minikin::MinikinPaint minikinPaint;
minikin::FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, paint, typeface);
const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface);
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index bfd816f..2e8aa58 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -37,11 +37,11 @@
ANDROID_API static minikin::FontStyle prepareMinikinPaint(minikin::MinikinPaint* minikinPaint,
const Paint* paint, const Typeface* typeface);
- ANDROID_API static minikin::Layout doLayout(const Paint* paint, int bidiFlags,
+ ANDROID_API static minikin::Layout doLayout(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf, size_t start, size_t count,
size_t bufSize);
- ANDROID_API static float measureText(const Paint* paint, int bidiFlags,
+ ANDROID_API static float measureText(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf, size_t start, size_t count,
size_t bufSize, float *advances);
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
index 75967e9..049018cc 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
@@ -73,7 +73,7 @@
* for reading back float buffers (skbug.com/6945).
*/
if (pixelConfig == kRGBA_half_GrPixelConfig &&
- !grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig)) {
+ !DeviceInfo::get()->extensions().hasRenderableFloatTextures()) {
ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
return CopyResult::DestinationInvalid;
}
@@ -84,57 +84,50 @@
sk_sp<SkImage> image(SkImage::MakeFromAdoptedTexture(grContext.get(), backendTexture,
kTopLeft_GrSurfaceOrigin));
if (image) {
- // Convert imgTransform matrix from right to left handed coordinate system.
- // If we have a matrix transformation in right handed coordinate system
- //|ScaleX, SkewX, TransX| same transform in left handed is |ScaleX, SkewX, TransX |
- //|SkewY, ScaleY, TransY| |-SkewY, -ScaleY, 1-TransY|
- //|0, 0, 1 | |0, 0, 1 |
- SkMatrix textureMatrix;
- textureMatrix.setIdentity();
- textureMatrix[SkMatrix::kMScaleX] = imgTransform[Matrix4::kScaleX];
- textureMatrix[SkMatrix::kMScaleY] = -imgTransform[Matrix4::kScaleY];
- textureMatrix[SkMatrix::kMSkewX] = imgTransform[Matrix4::kSkewX];
- textureMatrix[SkMatrix::kMSkewY] = -imgTransform[Matrix4::kSkewY];
- textureMatrix[SkMatrix::kMTransX] = imgTransform[Matrix4::kTranslateX];
- textureMatrix[SkMatrix::kMTransY] = 1-imgTransform[Matrix4::kTranslateY];
-
- // textureMatrix maps 2D texture coordinates of the form (s, t, 1) with s and t in the
- // inclusive range [0, 1] to the texture (see GLConsumer::getTransformMatrix comments).
- // Convert textureMatrix to translate in real texture dimensions. Texture width and
- // height are affected by the orientation (width and height swapped for 90/270 rotation).
- if (textureMatrix[SkMatrix::kMSkewX] >= 0.5f || textureMatrix[SkMatrix::kMSkewX] <= -0.5f) {
- textureMatrix[SkMatrix::kMTransX] *= imgHeight;
- textureMatrix[SkMatrix::kMTransY] *= imgWidth;
- } else {
- textureMatrix[SkMatrix::kMTransX] *= imgWidth;
- textureMatrix[SkMatrix::kMTransY] *= imgHeight;
+ int displayedWidth = imgWidth, displayedHeight = imgHeight;
+ // If this is a 90 or 270 degree rotation we need to swap width/height to get the device
+ // size.
+ if (imgTransform[Matrix4::kSkewX] >= 0.5f || imgTransform[Matrix4::kSkewX] <= -0.5f) {
+ std::swap(displayedWidth, displayedHeight);
}
-
- // convert to Skia data structures
- SkRect skiaSrcRect = srcRect.toSkRect();
- SkMatrix textureMatrixInv;
SkRect skiaDestRect = SkRect::MakeWH(bitmap->width(), bitmap->height());
- bool srcNotEmpty = false;
- if (textureMatrix.invert(&textureMatrixInv)) {
- if (skiaSrcRect.isEmpty()) {
- skiaSrcRect = SkRect::MakeIWH(imgWidth, imgHeight);
- srcNotEmpty = !skiaSrcRect.isEmpty();
- } else {
- // src and dest rectangles need to be converted into texture coordinates before the
- // rotation matrix is applied (because drawImageRect preconcat its matrix).
- textureMatrixInv.mapRect(&skiaSrcRect);
- srcNotEmpty = skiaSrcRect.intersect(SkRect::MakeIWH(imgWidth, imgHeight));
- }
- textureMatrixInv.mapRect(&skiaDestRect);
+ SkRect skiaSrcRect = srcRect.toSkRect();
+ if (skiaSrcRect.isEmpty()) {
+ skiaSrcRect = SkRect::MakeIWH(displayedWidth, displayedHeight);
}
+ bool srcNotEmpty = skiaSrcRect.intersect(SkRect::MakeIWH(displayedWidth, displayedHeight));
if (srcNotEmpty) {
+ SkMatrix textureMatrixInv;
+ imgTransform.copyTo(textureMatrixInv);
+ //TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed
+ // use bottom left origin and remove flipV and invert transformations.
+ SkMatrix flipV;
+ flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1);
+ textureMatrixInv.preConcat(flipV);
+ textureMatrixInv.preScale(1.0f/displayedWidth, 1.0f/displayedHeight);
+ textureMatrixInv.postScale(imgWidth, imgHeight);
+ SkMatrix textureMatrix;
+ if (!textureMatrixInv.invert(&textureMatrix)) {
+ textureMatrix = textureMatrixInv;
+ }
+
+ textureMatrixInv.mapRect(&skiaSrcRect);
+ textureMatrixInv.mapRect(&skiaDestRect);
+
// we render in an offscreen buffer to scale and to avoid an issue b/62262733
// with reading incorrect data from EGLImage backed SkImage (likely a driver bug)
sk_sp<SkSurface> scaledSurface = SkSurface::MakeRenderTarget(
grContext.get(), SkBudgeted::kYes, bitmap->info());
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
+ // Apply a filter, which is matching OpenGL pipeline readback behaviour. Filter usage
+ // is codified by tests using golden images like DecodeAccuracyTest.
+ if (skiaSrcRect.width() != bitmap->width()
+ || skiaSrcRect.height() != bitmap->height()) {
+ //TODO: apply filter always, but check if tests will be fine
+ paint.setFilterQuality(kLow_SkFilterQuality);
+ }
scaledSurface->getCanvas()->concat(textureMatrix);
scaledSurface->getCanvas()->drawImageRect(image, skiaSrcRect, skiaDestRect, &paint,
SkCanvas::kFast_SrcRectConstraint);
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 5fc5cb2..0ceca33 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -105,8 +105,8 @@
}
void RenderState::onVkContextDestroyed() {
- mLayerPool->clear();
std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerDestroyedVkContext);
+ destroyLayersInUpdater();
GpuMemoryTracker::onGpuContextDestroyed();
}
@@ -236,25 +236,11 @@
std::for_each(mActiveLayerUpdaters.begin(), mActiveLayerUpdaters.end(), destroyLayerInUpdater);
}
-class DecStrongTask : public renderthread::RenderTask {
-public:
- explicit DecStrongTask(VirtualLightRefBase* object) : mObject(object) {}
-
- virtual void run() override {
- mObject->decStrong(nullptr);
- mObject = nullptr;
- delete this;
- }
-
-private:
- VirtualLightRefBase* mObject;
-};
-
void RenderState::postDecStrong(VirtualLightRefBase* object) {
if (pthread_equal(mThreadId, pthread_self())) {
object->decStrong(nullptr);
} else {
- mRenderThread.queue(new DecStrongTask(object));
+ mRenderThread.queue().post([object]() { object->decStrong(nullptr); });
}
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index ad684db..36a0da1 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -181,13 +181,13 @@
mAnimationContext->destroy();
}
-void CanvasContext::setSurface(Surface* surface) {
+void CanvasContext::setSurface(sp<Surface>&& surface) {
ATRACE_CALL();
- mNativeSurface = surface;
+ mNativeSurface = std::move(surface);
ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::Srgb;
- bool hasSurface = mRenderPipeline->setSurface(surface, mSwapBehavior, colorMode);
+ bool hasSurface = mRenderPipeline->setSurface(mNativeSurface.get(), mSwapBehavior, colorMode);
mFrameNumber = -1;
@@ -203,15 +203,7 @@
mSwapBehavior = swapBehavior;
}
-void CanvasContext::initialize(Surface* surface) {
- setSurface(surface);
-}
-
-void CanvasContext::updateSurface(Surface* surface) {
- setSurface(surface);
-}
-
-bool CanvasContext::pauseSurface(Surface* surface) {
+bool CanvasContext::pauseSurface() {
return mRenderThread.removeFrameCallback(this);
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 4a5b2c7..f8a8775 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -117,9 +117,8 @@
// Won't take effect until next EGLSurface creation
void setSwapBehavior(SwapBehavior swapBehavior);
- void initialize(Surface* surface);
- void updateSurface(Surface* surface);
- bool pauseSurface(Surface* surface);
+ void setSurface(sp<Surface>&& surface);
+ bool pauseSurface();
void setStopped(bool stopped);
bool hasSurface() { return mNativeSurface.get(); }
@@ -205,8 +204,6 @@
// lifecycle tracking
friend class android::uirenderer::RenderState;
- void setSurface(Surface* window);
-
void freePrefetchedLayers();
bool isSwapChainStuffed();
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index a097272..0a94678 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -78,7 +78,7 @@
void DrawFrameTask::postAndWait() {
AutoMutex _lock(mLock);
- mRenderThread->queue(this);
+ mRenderThread->queue().post([this]() { run(); });
mSignal.wait(mLock);
}
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 83ecb98..4e4b6da 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -55,7 +55,7 @@
* tracked across many frames not just a single frame.
* It is the sync-state task, and will kick off the post-sync draw
*/
-class DrawFrameTask : public RenderTask {
+class DrawFrameTask {
public:
DrawFrameTask();
virtual ~DrawFrameTask();
@@ -72,7 +72,7 @@
int64_t* frameInfo() { return mFrameInfo; }
- virtual void run() override;
+ void run();
private:
void postAndWait();
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 0bb3889..cfc71cb 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -17,6 +17,7 @@
#pragma once
#include "FrameInfoVisualizer.h"
+#include "SwapBehavior.h"
#include <SkRect.h>
#include <utils/RefBase.h>
@@ -33,11 +34,6 @@
namespace renderthread {
-enum class SwapBehavior {
- kSwap_default,
- kSwap_discardBuffer,
-};
-
enum class MakeCurrentResult {
AlreadyCurrent,
Failed,
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index a6aa301..2f406da 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -36,46 +36,12 @@
namespace uirenderer {
namespace renderthread {
-#define ARGS(method) method ## Args
-
-#define CREATE_BRIDGE0(name) CREATE_BRIDGE(name,,,,,,,,)
-#define CREATE_BRIDGE1(name, a1) CREATE_BRIDGE(name, a1,,,,,,,)
-#define CREATE_BRIDGE2(name, a1, a2) CREATE_BRIDGE(name, a1,a2,,,,,,)
-#define CREATE_BRIDGE3(name, a1, a2, a3) CREATE_BRIDGE(name, a1,a2,a3,,,,,)
-#define CREATE_BRIDGE4(name, a1, a2, a3, a4) CREATE_BRIDGE(name, a1,a2,a3,a4,,,,)
-#define CREATE_BRIDGE5(name, a1, a2, a3, a4, a5) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,,,)
-#define CREATE_BRIDGE6(name, a1, a2, a3, a4, a5, a6) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,a6,,)
-#define CREATE_BRIDGE7(name, a1, a2, a3, a4, a5, a6, a7) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,a6,a7,)
-#define CREATE_BRIDGE(name, a1, a2, a3, a4, a5, a6, a7, a8) \
- typedef struct { \
- a1; a2; a3; a4; a5; a6; a7; a8; \
- } ARGS(name); \
- static_assert(std::is_trivially_destructible<ARGS(name)>::value, \
- "Error, ARGS must be trivially destructible!"); \
- static void* Bridge_ ## name(ARGS(name)* args)
-
-#define SETUP_TASK(method) \
- LOG_ALWAYS_FATAL_IF( METHOD_INVOKE_PAYLOAD_SIZE < sizeof(ARGS(method)), \
- "METHOD_INVOKE_PAYLOAD_SIZE %zu is smaller than sizeof(" #method "Args) %zu", \
- METHOD_INVOKE_PAYLOAD_SIZE, sizeof(ARGS(method))); \
- MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \
- ARGS(method) *args = (ARGS(method) *) task->payload()
-
-CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent,
- RenderNode* rootRenderNode, IContextFactory* contextFactory) {
- return CanvasContext::create(*args->thread, args->translucent,
- args->rootRenderNode, args->contextFactory);
-}
-
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory)
: mRenderThread(RenderThread::getInstance())
, mContext(nullptr) {
- SETUP_TASK(createContext);
- args->translucent = translucent;
- args->rootRenderNode = rootRenderNode;
- args->thread = &mRenderThread;
- args->contextFactory = contextFactory;
- mContext = (CanvasContext*) postAndWait(task);
+ mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* {
+ return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
+ });
mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
}
@@ -83,162 +49,91 @@
destroyContext();
}
-CREATE_BRIDGE1(destroyContext, CanvasContext* context) {
- delete args->context;
- return nullptr;
-}
-
void RenderProxy::destroyContext() {
if (mContext) {
- SETUP_TASK(destroyContext);
- args->context = mContext;
- mContext = nullptr;
mDrawFrameTask.setContext(nullptr, nullptr, nullptr);
// This is also a fence as we need to be certain that there are no
// outstanding mDrawFrame tasks posted before it is destroyed
- postAndWait(task);
+ mRenderThread.queue().runSync([this]() {
+ delete mContext;
+ });
+ mContext = nullptr;
}
}
-CREATE_BRIDGE2(setSwapBehavior, CanvasContext* context, SwapBehavior swapBehavior) {
- args->context->setSwapBehavior(args->swapBehavior);
- return nullptr;
-}
-
void RenderProxy::setSwapBehavior(SwapBehavior swapBehavior) {
- SETUP_TASK(setSwapBehavior);
- args->context = mContext;
- args->swapBehavior = swapBehavior;
- post(task);
-}
-
-CREATE_BRIDGE1(loadSystemProperties, CanvasContext* context) {
- bool needsRedraw = false;
- if (Caches::hasInstance()) {
- needsRedraw = Properties::load();
- }
- if (args->context->profiler().consumeProperties()) {
- needsRedraw = true;
- }
- return (void*) needsRedraw;
+ mRenderThread.queue().post([this, swapBehavior]() {
+ mContext->setSwapBehavior(swapBehavior);
+ });
}
bool RenderProxy::loadSystemProperties() {
- SETUP_TASK(loadSystemProperties);
- args->context = mContext;
- return (bool) postAndWait(task);
-}
-
-CREATE_BRIDGE2(setName, CanvasContext* context, const char* name) {
- args->context->setName(std::string(args->name));
- return nullptr;
+ return mRenderThread.queue().runSync([this]() -> bool {
+ bool needsRedraw = false;
+ if (Caches::hasInstance()) {
+ needsRedraw = Properties::load();
+ }
+ if (mContext->profiler().consumeProperties()) {
+ needsRedraw = true;
+ }
+ return needsRedraw;
+ });
}
void RenderProxy::setName(const char* name) {
- SETUP_TASK(setName);
- args->context = mContext;
- args->name = name;
- postAndWait(task); // block since name/value pointers owned by caller
-}
-
-CREATE_BRIDGE2(initialize, CanvasContext* context, Surface* surface) {
- args->context->initialize(args->surface);
- return nullptr;
+ // block since name/value pointers owned by caller
+ // TODO: Support move arguments
+ mRenderThread.queue().runSync([this, name]() {
+ mContext->setName(std::string(name));
+ });
}
void RenderProxy::initialize(const sp<Surface>& surface) {
- SETUP_TASK(initialize);
- args->context = mContext;
- args->surface = surface.get();
- post(task);
-}
-
-CREATE_BRIDGE2(updateSurface, CanvasContext* context, Surface* surface) {
- args->context->updateSurface(args->surface);
- return nullptr;
+ mRenderThread.queue().post([this, surf = surface]() mutable {
+ mContext->setSurface(std::move(surf));
+ });
}
void RenderProxy::updateSurface(const sp<Surface>& surface) {
- SETUP_TASK(updateSurface);
- args->context = mContext;
- args->surface = surface.get();
- post(task);
-}
-
-CREATE_BRIDGE2(pauseSurface, CanvasContext* context, Surface* surface) {
- return (void*) args->context->pauseSurface(args->surface);
+ mRenderThread.queue().post([this, surf = surface]() mutable {
+ mContext->setSurface(std::move(surf));
+ });
}
bool RenderProxy::pauseSurface(const sp<Surface>& surface) {
- SETUP_TASK(pauseSurface);
- args->context = mContext;
- args->surface = surface.get();
- return (bool) postAndWait(task);
-}
-
-CREATE_BRIDGE2(setStopped, CanvasContext* context, bool stopped) {
- args->context->setStopped(args->stopped);
- return nullptr;
+ return mRenderThread.queue().runSync([this]() -> bool {
+ return mContext->pauseSurface();
+ });
}
void RenderProxy::setStopped(bool stopped) {
- SETUP_TASK(setStopped);
- args->context = mContext;
- args->stopped = stopped;
- postAndWait(task);
+ mRenderThread.queue().runSync([this, stopped]() {
+ mContext->setStopped(stopped);
+ });
}
-CREATE_BRIDGE4(setup, CanvasContext* context,
- float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
- args->context->setup(args->lightRadius,
- args->ambientShadowAlpha, args->spotShadowAlpha);
- return nullptr;
-}
-
-void RenderProxy::setup(float lightRadius,
- uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
- SETUP_TASK(setup);
- args->context = mContext;
- args->lightRadius = lightRadius;
- args->ambientShadowAlpha = ambientShadowAlpha;
- args->spotShadowAlpha = spotShadowAlpha;
- post(task);
-}
-
-CREATE_BRIDGE2(setLightCenter, CanvasContext* context, Vector3 lightCenter) {
- args->context->setLightCenter(args->lightCenter);
- return nullptr;
+void RenderProxy::setup(float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+ mRenderThread.queue().post([=]() {
+ mContext->setup(lightRadius, ambientShadowAlpha, spotShadowAlpha);
+ });
}
void RenderProxy::setLightCenter(const Vector3& lightCenter) {
- SETUP_TASK(setLightCenter);
- args->context = mContext;
- args->lightCenter = lightCenter;
- post(task);
-}
-
-CREATE_BRIDGE2(setOpaque, CanvasContext* context, bool opaque) {
- args->context->setOpaque(args->opaque);
- return nullptr;
+ mRenderThread.queue().post([=]() {
+ mContext->setLightCenter(lightCenter);
+ });
}
void RenderProxy::setOpaque(bool opaque) {
- SETUP_TASK(setOpaque);
- args->context = mContext;
- args->opaque = opaque;
- post(task);
-}
-
-CREATE_BRIDGE2(setWideGamut, CanvasContext* context, bool wideGamut) {
- args->context->setWideGamut(args->wideGamut);
- return nullptr;
+ mRenderThread.queue().post([=]() {
+ mContext->setOpaque(opaque);
+ });
}
void RenderProxy::setWideGamut(bool wideGamut) {
- SETUP_TASK(setWideGamut);
- args->context = mContext;
- args->wideGamut = wideGamut;
- post(task);
+ mRenderThread.queue().post([=]() {
+ mContext->setWideGamut(wideGamut);
+ });
}
int64_t* RenderProxy::frameInfo() {
@@ -249,77 +144,45 @@
return mDrawFrameTask.drawFrame();
}
-CREATE_BRIDGE1(destroy, CanvasContext* context) {
- args->context->destroy();
- return nullptr;
-}
-
void RenderProxy::destroy() {
- SETUP_TASK(destroy);
- args->context = mContext;
// destroyCanvasAndSurface() needs a fence as when it returns the
// underlying BufferQueue is going to be released from under
// the render thread.
- postAndWait(task);
-}
-
-CREATE_BRIDGE2(invokeFunctor, RenderThread* thread, Functor* functor) {
- CanvasContext::invokeFunctor(*args->thread, args->functor);
- return nullptr;
+ mRenderThread.queue().runSync([=]() {
+ mContext->destroy();
+ });
}
void RenderProxy::invokeFunctor(Functor* functor, bool waitForCompletion) {
ATRACE_CALL();
RenderThread& thread = RenderThread::getInstance();
- SETUP_TASK(invokeFunctor);
- args->thread = &thread;
- args->functor = functor;
+ auto invoke = [&thread, functor]() { CanvasContext::invokeFunctor(thread, functor); };
if (waitForCompletion) {
// waitForCompletion = true is expected to be fairly rare and only
// happen in destruction. Thus it should be fine to temporarily
// create a Mutex
- staticPostAndWait(task);
+ thread.queue().runSync(std::move(invoke));
} else {
- thread.queue(task);
+ thread.queue().post(std::move(invoke));
}
}
-CREATE_BRIDGE1(createTextureLayer, CanvasContext* context) {
- return args->context->createTextureLayer();
-}
-
DeferredLayerUpdater* RenderProxy::createTextureLayer() {
- SETUP_TASK(createTextureLayer);
- args->context = mContext;
- void* retval = postAndWait(task);
- DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(retval);
- return layer;
-}
-
-CREATE_BRIDGE2(buildLayer, CanvasContext* context, RenderNode* node) {
- args->context->buildLayer(args->node);
- return nullptr;
+ return mRenderThread.queue().runSync([this]() -> auto {
+ return mContext->createTextureLayer();
+ });
}
void RenderProxy::buildLayer(RenderNode* node) {
- SETUP_TASK(buildLayer);
- args->context = mContext;
- args->node = node;
- postAndWait(task);
-}
-
-CREATE_BRIDGE3(copyLayerInto, CanvasContext* context, DeferredLayerUpdater* layer,
- SkBitmap* bitmap) {
- bool success = args->context->copyLayerInto(args->layer, args->bitmap);
- return (void*) success;
+ mRenderThread.queue().runSync([&]() {
+ mContext->buildLayer(node);
+ });
}
bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) {
- SETUP_TASK(copyLayerInto);
- args->context = mContext;
- args->layer = layer;
- args->bitmap = &bitmap;
- return (bool) postAndWait(task);
+ return mRenderThread.queue().runSync([&]() -> bool {
+ return mContext->copyLayerInto(layer, &bitmap);
+ });
}
void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -330,302 +193,154 @@
mDrawFrameTask.removeLayerUpdate(layer);
}
-CREATE_BRIDGE1(detachSurfaceTexture, DeferredLayerUpdater* layer) {
- args->layer->detachSurfaceTexture();
- return nullptr;
-}
-
void RenderProxy::detachSurfaceTexture(DeferredLayerUpdater* layer) {
- SETUP_TASK(detachSurfaceTexture);
- args->layer = layer;
- postAndWait(task);
-}
-
-CREATE_BRIDGE1(destroyHardwareResources, CanvasContext* context) {
- args->context->destroyHardwareResources();
- return nullptr;
+ return mRenderThread.queue().runSync([&]() {
+ layer->detachSurfaceTexture();
+ });
}
void RenderProxy::destroyHardwareResources() {
- SETUP_TASK(destroyHardwareResources);
- args->context = mContext;
- postAndWait(task);
-}
-
-CREATE_BRIDGE2(trimMemory, RenderThread* thread, int level) {
- CanvasContext::trimMemory(*args->thread, args->level);
- return nullptr;
+ return mRenderThread.queue().runSync([&]() {
+ mContext->destroyHardwareResources();
+ });
}
void RenderProxy::trimMemory(int level) {
// Avoid creating a RenderThread to do a trimMemory.
if (RenderThread::hasInstance()) {
RenderThread& thread = RenderThread::getInstance();
- SETUP_TASK(trimMemory);
- args->thread = &thread;
- args->level = level;
- thread.queue(task);
+ thread.queue().post([&thread, level]() {
+ CanvasContext::trimMemory(thread, level);
+ });
}
}
-CREATE_BRIDGE2(overrideProperty, const char* name, const char* value) {
- Properties::overrideProperty(args->name, args->value);
- return nullptr;
-}
-
void RenderProxy::overrideProperty(const char* name, const char* value) {
- SETUP_TASK(overrideProperty);
- args->name = name;
- args->value = value;
- staticPostAndWait(task); // expensive, but block here since name/value pointers owned by caller
+ // expensive, but block here since name/value pointers owned by caller
+ RenderThread::getInstance().queue().runSync([&]() {
+ Properties::overrideProperty(name, value);
+ });
}
-CREATE_BRIDGE0(fence) {
- // Intentionally empty
- return nullptr;
-}
-
-template <typename T>
-void UNUSED(T t) {}
-
void RenderProxy::fence() {
- SETUP_TASK(fence);
- UNUSED(args);
- postAndWait(task);
+ mRenderThread.queue().runSync([](){});
}
void RenderProxy::staticFence() {
- SETUP_TASK(fence);
- UNUSED(args);
- staticPostAndWait(task);
-}
-
-CREATE_BRIDGE1(stopDrawing, CanvasContext* context) {
- args->context->stopDrawing();
- return nullptr;
+ RenderThread::getInstance().queue().runSync([](){});
}
void RenderProxy::stopDrawing() {
- SETUP_TASK(stopDrawing);
- args->context = mContext;
- postAndWait(task);
-}
-
-CREATE_BRIDGE1(notifyFramePending, CanvasContext* context) {
- args->context->notifyFramePending();
- return nullptr;
+ mRenderThread.queue().runSync([this]() {
+ mContext->stopDrawing();
+ });
}
void RenderProxy::notifyFramePending() {
- SETUP_TASK(notifyFramePending);
- args->context = mContext;
- mRenderThread.queueAtFront(task);
-}
-
-CREATE_BRIDGE4(dumpProfileInfo, CanvasContext* context, RenderThread* thread,
- int fd, int dumpFlags) {
- args->context->profiler().dumpData(args->fd);
- if (args->dumpFlags & DumpFlags::FrameStats) {
- args->context->dumpFrames(args->fd);
- }
- if (args->dumpFlags & DumpFlags::JankStats) {
- args->thread->globalProfileData()->dump(args->fd);
- }
- if (args->dumpFlags & DumpFlags::Reset) {
- args->context->resetFrameStats();
- }
- return nullptr;
+ mRenderThread.queue().post([this]() {
+ mContext->notifyFramePending();
+ });
}
void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
- SETUP_TASK(dumpProfileInfo);
- args->context = mContext;
- args->thread = &mRenderThread;
- args->fd = fd;
- args->dumpFlags = dumpFlags;
- postAndWait(task);
-}
-
-CREATE_BRIDGE1(resetProfileInfo, CanvasContext* context) {
- args->context->resetFrameStats();
- return nullptr;
+ mRenderThread.queue().runSync([&]() {
+ mContext->profiler().dumpData(fd);
+ if (dumpFlags & DumpFlags::FrameStats) {
+ mContext->dumpFrames(fd);
+ }
+ if (dumpFlags & DumpFlags::JankStats) {
+ mRenderThread.globalProfileData()->dump(fd);
+ }
+ if (dumpFlags & DumpFlags::Reset) {
+ mContext->resetFrameStats();
+ }
+ });
}
void RenderProxy::resetProfileInfo() {
- SETUP_TASK(resetProfileInfo);
- args->context = mContext;
- postAndWait(task);
+ mRenderThread.queue().runSync([=]() {
+ mContext->resetFrameStats();
+ });
}
-CREATE_BRIDGE2(frameTimePercentile, RenderThread* thread, int percentile) {
- return reinterpret_cast<void*>(static_cast<uintptr_t>(
- args->thread->globalProfileData()->findPercentile(args->percentile)));
-}
-
-uint32_t RenderProxy::frameTimePercentile(int p) {
- SETUP_TASK(frameTimePercentile);
- args->thread = &mRenderThread;
- args->percentile = p;
- return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(
- postAndWait(task)));
-}
-
-CREATE_BRIDGE2(dumpGraphicsMemory, int fd, RenderThread* thread) {
- args->thread->dumpGraphicsMemory(args->fd);
- return nullptr;
+uint32_t RenderProxy::frameTimePercentile(int percentile) {
+ return mRenderThread.queue().runSync([&]() -> auto {
+ return mRenderThread.globalProfileData()->findPercentile(percentile);
+ });
}
void RenderProxy::dumpGraphicsMemory(int fd) {
- if (!RenderThread::hasInstance()) return;
- SETUP_TASK(dumpGraphicsMemory);
- args->fd = fd;
- args->thread = &RenderThread::getInstance();
- staticPostAndWait(task);
-}
-
-CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) {
- args->thread->globalProfileData().switchStorageToAshmem(args->fd);
- close(args->fd);
- return nullptr;
+ auto& thread = RenderThread::getInstance();
+ thread.queue().runSync([&]() {
+ thread.dumpGraphicsMemory(fd);
+ });
}
void RenderProxy::setProcessStatsBuffer(int fd) {
- SETUP_TASK(setProcessStatsBuffer);
auto& rt = RenderThread::getInstance();
- args->thread = &rt;
- args->fd = dup(fd);
- rt.queue(task);
-}
-
-CREATE_BRIDGE1(rotateProcessStatsBuffer, RenderThread* thread) {
- args->thread->globalProfileData().rotateStorage();
- return nullptr;
+ rt.queue().post([&rt, fd = dup(fd)]() {
+ rt.globalProfileData().switchStorageToAshmem(fd);
+ close(fd);
+ });
}
void RenderProxy::rotateProcessStatsBuffer() {
- SETUP_TASK(rotateProcessStatsBuffer);
auto& rt = RenderThread::getInstance();
- args->thread = &rt;
- rt.queue(task);
+ rt.queue().post([&rt]() {
+ rt.globalProfileData().rotateStorage();
+ });
}
int RenderProxy::getRenderThreadTid() {
return mRenderThread.getTid();
}
-CREATE_BRIDGE3(addRenderNode, CanvasContext* context, RenderNode* node, bool placeFront) {
- args->context->addRenderNode(args->node, args->placeFront);
- return nullptr;
-}
-
void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
- SETUP_TASK(addRenderNode);
- args->context = mContext;
- args->node = node;
- args->placeFront = placeFront;
- post(task);
-}
-
-CREATE_BRIDGE2(removeRenderNode, CanvasContext* context, RenderNode* node) {
- args->context->removeRenderNode(args->node);
- return nullptr;
+ mRenderThread.queue().post([=]() {
+ mContext->addRenderNode(node, placeFront);
+ });
}
void RenderProxy::removeRenderNode(RenderNode* node) {
- SETUP_TASK(removeRenderNode);
- args->context = mContext;
- args->node = node;
- post(task);
-}
-
-CREATE_BRIDGE2(drawRenderNode, CanvasContext* context, RenderNode* node) {
- args->context->prepareAndDraw(args->node);
- return nullptr;
+ mRenderThread.queue().post([=]() {
+ mContext->removeRenderNode(node);
+ });
}
void RenderProxy::drawRenderNode(RenderNode* node) {
- SETUP_TASK(drawRenderNode);
- args->context = mContext;
- args->node = node;
- // Be pseudo-thread-safe and don't use any member variables
- staticPostAndWait(task);
+ mRenderThread.queue().runSync([=]() {
+ mContext->prepareAndDraw(node);
+ });
}
void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) {
mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
}
-CREATE_BRIDGE1(serializeDisplayListTree, CanvasContext* context) {
- args->context->serializeDisplayListTree();
- return nullptr;
-}
-
void RenderProxy::serializeDisplayListTree() {
- SETUP_TASK(serializeDisplayListTree);
- args->context = mContext;
- post(task);
+ mRenderThread.queue().post([=]() {
+ mContext->serializeDisplayListTree();
+ });
}
-CREATE_BRIDGE2(addFrameMetricsObserver, CanvasContext* context,
- FrameMetricsObserver* frameStatsObserver) {
- args->context->addFrameMetricsObserver(args->frameStatsObserver);
- if (args->frameStatsObserver != nullptr) {
- args->frameStatsObserver->decStrong(args->context);
- }
- return nullptr;
+void RenderProxy::addFrameMetricsObserver(FrameMetricsObserver* observerPtr) {
+ mRenderThread.queue().post([this, observer = sp{observerPtr}]() {
+ mContext->addFrameMetricsObserver(observer.get());
+ });
}
-void RenderProxy::addFrameMetricsObserver(FrameMetricsObserver* observer) {
- SETUP_TASK(addFrameMetricsObserver);
- args->context = mContext;
- args->frameStatsObserver = observer;
- if (observer != nullptr) {
- observer->incStrong(mContext);
- }
- post(task);
-}
-
-CREATE_BRIDGE2(removeFrameMetricsObserver, CanvasContext* context,
- FrameMetricsObserver* frameStatsObserver) {
- args->context->removeFrameMetricsObserver(args->frameStatsObserver);
- if (args->frameStatsObserver != nullptr) {
- args->frameStatsObserver->decStrong(args->context);
- }
- return nullptr;
-}
-
-void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observer) {
- SETUP_TASK(removeFrameMetricsObserver);
- args->context = mContext;
- args->frameStatsObserver = observer;
- if (observer != nullptr) {
- observer->incStrong(mContext);
- }
- post(task);
-}
-
-CREATE_BRIDGE4(copySurfaceInto, RenderThread* thread,
- Surface* surface, Rect srcRect, SkBitmap* bitmap) {
- return (void*)args->thread->readback().copySurfaceInto(*args->surface,
- args->srcRect, args->bitmap);
+void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observerPtr) {
+ mRenderThread.queue().post([this, observer = sp{observerPtr}]() {
+ mContext->removeFrameMetricsObserver(observer.get());
+ });
}
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) ));
-}
-
-CREATE_BRIDGE2(prepareToDraw, RenderThread* thread, Bitmap* bitmap) {
- CanvasContext::prepareToDraw(*args->thread, args->bitmap);
- args->bitmap->unref();
- args->bitmap = nullptr;
- return nullptr;
+ auto& thread = RenderThread::getInstance();
+ return static_cast<int>(thread.queue().runSync([&]() -> auto {
+ return thread.readback().copySurfaceInto(*surface, Rect(left, top, right, bottom), bitmap);
+ }));
}
void RenderProxy::prepareToDraw(Bitmap& bitmap) {
@@ -635,10 +350,11 @@
// window or not.
if (!RenderThread::hasInstance()) return;
RenderThread* renderThread = &RenderThread::getInstance();
- SETUP_TASK(prepareToDraw);
- args->thread = renderThread;
bitmap.ref();
- args->bitmap = &bitmap;
+ auto task = [renderThread, &bitmap]() {
+ CanvasContext::prepareToDraw(*renderThread, &bitmap);
+ bitmap.unref();
+ };
nsecs_t lastVsync = renderThread->timeLord().latestVsync();
nsecs_t estimatedNextVsync = lastVsync + renderThread->timeLord().frameIntervalNanos();
nsecs_t timeToNextVsync = estimatedNextVsync - systemTime(CLOCK_MONOTONIC);
@@ -648,27 +364,17 @@
// TODO: Make this concept a first-class supported thing? RT could use
// knowledge of pending draws to better schedule this task
if (timeToNextVsync > -6_ms && timeToNextVsync < 1_ms) {
- renderThread->queueAt(task, estimatedNextVsync + 8_ms);
+ renderThread->queue().postAt(estimatedNextVsync + 8_ms, task);
} else {
- renderThread->queue(task);
+ renderThread->queue().post(task);
}
}
-CREATE_BRIDGE2(allocateHardwareBitmap, RenderThread* thread, SkBitmap* bitmap) {
- sk_sp<Bitmap> hardwareBitmap = args->thread->allocateHardwareBitmap(*args->bitmap);
- return hardwareBitmap.release();
-}
-
sk_sp<Bitmap> RenderProxy::allocateHardwareBitmap(SkBitmap& bitmap) {
- SETUP_TASK(allocateHardwareBitmap);
- args->bitmap = &bitmap;
- args->thread = &RenderThread::getInstance();
- sk_sp<Bitmap> hardwareBitmap(reinterpret_cast<Bitmap*>(staticPostAndWait(task)));
- return hardwareBitmap;
-}
-
-CREATE_BRIDGE3(copyGraphicBufferInto, RenderThread* thread, GraphicBuffer* buffer, SkBitmap* bitmap) {
- return (void*) args->thread->readback().copyGraphicBufferInto(args->buffer, args->bitmap);
+ auto& thread = RenderThread::getInstance();
+ return thread.queue().runSync([&]() -> auto {
+ return thread.allocateHardwareBitmap(bitmap);
+ });
}
int RenderProxy::copyGraphicBufferInto(GraphicBuffer* buffer, SkBitmap* bitmap) {
@@ -677,80 +383,36 @@
//TODO: fix everything that hits this. We should never be triggering a readback ourselves.
return (int) thread.readback().copyGraphicBufferInto(buffer, bitmap);
} else {
- SETUP_TASK(copyGraphicBufferInto);
- args->thread = &thread;
- args->bitmap = bitmap;
- args->buffer = buffer;
- return static_cast<int>(reinterpret_cast<intptr_t>(staticPostAndWait(task)));
+ return thread.queue().runSync([&]() -> int {
+ return (int) thread.readback().copyGraphicBufferInto(buffer, bitmap);
+ });
}
}
-CREATE_BRIDGE2(onBitmapDestroyed, RenderThread* thread, uint32_t pixelRefId) {
- args->thread->renderState().onBitmapDestroyed(args->pixelRefId);
- return nullptr;
-}
-
void RenderProxy::onBitmapDestroyed(uint32_t pixelRefId) {
if (!RenderThread::hasInstance()) return;
- SETUP_TASK(onBitmapDestroyed);
RenderThread& thread = RenderThread::getInstance();
- args->thread = &thread;
- args->pixelRefId = pixelRefId;
- thread.queue(task);
+ thread.queue().post([&thread, pixelRefId]() {
+ thread.renderState().onBitmapDestroyed(pixelRefId);
+ });
}
void RenderProxy::disableVsync() {
Properties::disableVsync = true;
}
-void RenderProxy::post(RenderTask* task) {
- mRenderThread.queue(task);
-}
-
-CREATE_BRIDGE1(repackVectorDrawableAtlas, RenderThread* thread) {
- args->thread->cacheManager().acquireVectorDrawableAtlas()->repackIfNeeded(
- args->thread->getGrContext());
- return nullptr;
-}
-
void RenderProxy::repackVectorDrawableAtlas() {
RenderThread& thread = RenderThread::getInstance();
- SETUP_TASK(repackVectorDrawableAtlas);
- args->thread = &thread;
- thread.queue(task);
-}
-
-CREATE_BRIDGE1(releaseVDAtlasEntries, RenderThread* thread) {
- args->thread->cacheManager().acquireVectorDrawableAtlas()->delayedReleaseEntries();
- return nullptr;
+ thread.queue().post([&thread]() {
+ thread.cacheManager().acquireVectorDrawableAtlas()->repackIfNeeded(thread.getGrContext());
+ });
}
void RenderProxy::releaseVDAtlasEntries() {
RenderThread& thread = RenderThread::getInstance();
- SETUP_TASK(releaseVDAtlasEntries);
- args->thread = &thread;
- thread.queue(task);
-}
-
-void* RenderProxy::postAndWait(MethodInvokeRenderTask* task) {
- void* retval;
- task->setReturnPtr(&retval);
- SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
- AutoMutex _lock(mSyncMutex);
- mRenderThread.queue(&syncTask);
- while (!syncTask.hasRun()) {
- mSyncCondition.wait(mSyncMutex);
- }
- return retval;
-}
-
-void* RenderProxy::staticPostAndWait(MethodInvokeRenderTask* task) {
- RenderThread& thread = RenderThread::getInstance();
- LOG_ALWAYS_FATAL_IF(gettid() == thread.getTid());
- void* retval;
- task->setReturnPtr(&retval);
- thread.queueAndWait(task);
- return retval;
+ thread.queue().post([&thread]() {
+ thread.cacheManager().acquireVectorDrawableAtlas()->delayedReleaseEntries();
+ });
}
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 9440b15..b46d9cc 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -17,22 +17,16 @@
#ifndef RENDERPROXY_H_
#define RENDERPROXY_H_
-#include "RenderTask.h"
-
#include <cutils/compiler.h>
-#include <EGL/egl.h>
#include <SkBitmap.h>
-#include <utils/Condition.h>
#include <utils/Functor.h>
-#include <utils/Mutex.h>
-#include <utils/Timers.h>
-#include <utils/StrongPointer.h>
+#include <gui/Surface.h>
-#include "../Caches.h"
#include "../FrameMetricsObserver.h"
#include "../IContextFactory.h"
-#include "CanvasContext.h"
+#include "hwui/Bitmap.h"
#include "DrawFrameTask.h"
+#include "SwapBehavior.h"
namespace android {
class GraphicBuffer;
@@ -41,13 +35,11 @@
class DeferredLayerUpdater;
class RenderNode;
-class DisplayList;
-class Layer;
class Rect;
namespace renderthread {
-class ErrorChannel;
+class CanvasContext;
class RenderThread;
class RenderProxyBridge;
@@ -151,16 +143,8 @@
DrawFrameTask mDrawFrameTask;
- Mutex mSyncMutex;
- Condition mSyncCondition;
-
void destroyContext();
- void post(RenderTask* task);
- void* postAndWait(MethodInvokeRenderTask* task);
-
- static void* staticPostAndWait(MethodInvokeRenderTask* task);
-
// Friend class to help with bridging
friend class RenderProxyBridge;
};
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 51e9374..f3bb120 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -49,101 +49,6 @@
// Slight delay to give the UI time to push us a new frame before we replay
static const nsecs_t DISPATCH_FRAME_CALLBACKS_DELAY = milliseconds_to_nanoseconds(4);
-TaskQueue::TaskQueue() : mHead(nullptr), mTail(nullptr) {}
-
-RenderTask* TaskQueue::next() {
- RenderTask* ret = mHead;
- if (ret) {
- mHead = ret->mNext;
- if (!mHead) {
- mTail = nullptr;
- }
- ret->mNext = nullptr;
- }
- return ret;
-}
-
-RenderTask* TaskQueue::peek() {
- return mHead;
-}
-
-void TaskQueue::queue(RenderTask* task) {
- // Since the RenderTask itself forms the linked list it is not allowed
- // to have the same task queued twice
- LOG_ALWAYS_FATAL_IF(task->mNext || mTail == task, "Task is already in the queue!");
- if (mTail) {
- // Fast path if we can just append
- if (mTail->mRunAt <= task->mRunAt) {
- mTail->mNext = task;
- mTail = task;
- } else {
- // Need to find the proper insertion point
- RenderTask* previous = nullptr;
- RenderTask* next = mHead;
- while (next && next->mRunAt <= task->mRunAt) {
- previous = next;
- next = next->mNext;
- }
- if (!previous) {
- task->mNext = mHead;
- mHead = task;
- } else {
- previous->mNext = task;
- if (next) {
- task->mNext = next;
- } else {
- mTail = task;
- }
- }
- }
- } else {
- mTail = mHead = task;
- }
-}
-
-void TaskQueue::queueAtFront(RenderTask* task) {
- LOG_ALWAYS_FATAL_IF(task->mNext || mHead == task, "Task is already in the queue!");
- if (mTail) {
- task->mNext = mHead;
- mHead = task;
- } else {
- mTail = mHead = task;
- }
-}
-
-void TaskQueue::remove(RenderTask* task) {
- // TaskQueue is strict here to enforce that users are keeping track of
- // their RenderTasks due to how their memory is managed
- LOG_ALWAYS_FATAL_IF(!task->mNext && mTail != task,
- "Cannot remove a task that isn't in the queue!");
-
- // If task is the head we can just call next() to pop it off
- // Otherwise we need to scan through to find the task before it
- if (peek() == task) {
- next();
- } else {
- RenderTask* previous = mHead;
- while (previous->mNext != task) {
- previous = previous->mNext;
- }
- previous->mNext = task->mNext;
- if (mTail == task) {
- mTail = previous;
- }
- }
-}
-
-class DispatchFrameCallbacks : public RenderTask {
-private:
- RenderThread* mRenderThread;
-public:
- explicit DispatchFrameCallbacks(RenderThread* rt) : mRenderThread(rt) {}
-
- virtual void run() override {
- mRenderThread->dispatchFrameCallbacks();
- }
-};
-
static bool gHasRenderThreadInstance = false;
bool RenderThread::hasInstance() {
@@ -159,19 +64,15 @@
return *sInstance;
}
-RenderThread::RenderThread() : Thread(true)
- , mNextWakeup(LLONG_MAX)
+RenderThread::RenderThread() : ThreadBase()
, mDisplayEventReceiver(nullptr)
, mVsyncRequested(false)
, mFrameCallbackTaskPending(false)
- , mFrameCallbackTask(nullptr)
, mRenderState(nullptr)
, mEglManager(nullptr)
, mVkManager(nullptr) {
Properties::load();
- mFrameCallbackTask = new DispatchFrameCallbacks(this);
- mLooper = new Looper(false);
- run("RenderThread");
+ start("RenderThread");
}
RenderThread::~RenderThread() {
@@ -321,7 +222,9 @@
ATRACE_NAME("queue mFrameCallbackTask");
mFrameCallbackTaskPending = true;
nsecs_t runAt = (vsyncEvent + DISPATCH_FRAME_CALLBACKS_DELAY);
- queueAt(mFrameCallbackTask, runAt);
+ queue().postAt(runAt, [this]() {
+ dispatchFrameCallbacks();
+ });
}
}
}
@@ -356,35 +259,9 @@
setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY);
initThreadLocals();
- int timeoutMillis = -1;
- for (;;) {
- int result = mLooper->pollOnce(timeoutMillis);
- LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
- "RenderThread Looper POLL_ERROR!");
-
- nsecs_t nextWakeup;
- {
- FatVector<RenderTask*, 10> workQueue;
- // Process our queue, if we have anything. By first acquiring
- // all the pending events then processing them we avoid vsync
- // starvation if more tasks are queued while we are processing tasks.
- while (RenderTask* task = nextTask(&nextWakeup)) {
- workQueue.push_back(task);
- }
- for (auto task : workQueue) {
- task->run();
- // task may have deleted itself, do not reference it again
- }
- }
- if (nextWakeup == LLONG_MAX) {
- timeoutMillis = -1;
- } else {
- nsecs_t timeoutNanos = nextWakeup - systemTime(SYSTEM_TIME_MONOTONIC);
- timeoutMillis = nanoseconds_to_milliseconds(timeoutNanos);
- if (timeoutMillis < 0) {
- timeoutMillis = 0;
- }
- }
+ while (true) {
+ waitForWork();
+ processQueue();
if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {
drainDisplayEventQueue();
@@ -406,46 +283,6 @@
return false;
}
-void RenderThread::queue(RenderTask* task) {
- AutoMutex _lock(mLock);
- mQueue.queue(task);
- if (mNextWakeup && task->mRunAt < mNextWakeup) {
- mNextWakeup = 0;
- mLooper->wake();
- }
-}
-
-void RenderThread::queueAndWait(RenderTask* task) {
- // These need to be local to the thread to avoid the Condition
- // signaling the wrong thread. The easiest way to achieve that is to just
- // make this on the stack, although that has a slight cost to it
- Mutex mutex;
- Condition condition;
- SignalingRenderTask syncTask(task, &mutex, &condition);
-
- AutoMutex _lock(mutex);
- queue(&syncTask);
- while (!syncTask.hasRun()) {
- condition.wait(mutex);
- }
-}
-
-void RenderThread::queueAtFront(RenderTask* task) {
- AutoMutex _lock(mLock);
- mQueue.queueAtFront(task);
- mLooper->wake();
-}
-
-void RenderThread::queueAt(RenderTask* task, nsecs_t runAtNs) {
- task->mRunAt = runAtNs;
- queue(task);
-}
-
-void RenderThread::remove(RenderTask* task) {
- AutoMutex _lock(mLock);
- mQueue.remove(task);
-}
-
void RenderThread::postFrameCallback(IFrameCallback* callback) {
mPendingRegistrationFrameCallbacks.insert(callback);
}
@@ -463,26 +300,6 @@
}
}
-RenderTask* RenderThread::nextTask(nsecs_t* nextWakeup) {
- AutoMutex _lock(mLock);
- RenderTask* next = mQueue.peek();
- if (!next) {
- mNextWakeup = LLONG_MAX;
- } else {
- mNextWakeup = next->mRunAt;
- // Most tasks won't be delayed, so avoid unnecessary systemTime() calls
- if (next->mRunAt <= 0 || next->mRunAt <= systemTime(SYSTEM_TIME_MONOTONIC)) {
- next = mQueue.next();
- } else {
- next = nullptr;
- }
- }
- if (nextWakeup) {
- *nextWakeup = mNextWakeup;
- }
- return next;
-}
-
sk_sp<Bitmap> RenderThread::allocateHardwareBitmap(SkBitmap& skBitmap) {
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 30884b5..e1d61c5 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -22,6 +22,7 @@
#include "../JankTracker.h"
#include "CacheManager.h"
#include "TimeLord.h"
+#include "thread/ThreadBase.h"
#include <GrContext.h>
#include <cutils/compiler.h>
@@ -31,7 +32,9 @@
#include <utils/Thread.h>
#include <memory>
+#include <mutex>
#include <set>
+#include <thread/ThreadBase.h>
namespace android {
@@ -47,26 +50,10 @@
namespace renderthread {
class CanvasContext;
-class DispatchFrameCallbacks;
class EglManager;
class RenderProxy;
class VulkanManager;
-class TaskQueue {
-public:
- TaskQueue();
-
- RenderTask* next();
- void queue(RenderTask* task);
- void queueAtFront(RenderTask* task);
- RenderTask* peek();
- void remove(RenderTask* task);
-
-private:
- RenderTask* mHead;
- RenderTask* mTail;
-};
-
// Mimics android.view.Choreographer.FrameCallback
class IFrameCallback {
public:
@@ -76,16 +63,11 @@
~IFrameCallback() {}
};
-class ANDROID_API RenderThread : public Thread {
+class RenderThread : private ThreadBase {
PREVENT_COPY_AND_ASSIGN(RenderThread);
public:
- // RenderThread takes complete ownership of tasks that are queued
- // and will delete them after they are run
- ANDROID_API void queue(RenderTask* task);
- ANDROID_API void queueAndWait(RenderTask* task);
- ANDROID_API void queueAtFront(RenderTask* task);
- void queueAt(RenderTask* task, nsecs_t runAtNs);
- void remove(RenderTask* task);
+
+ WorkQueue& queue() { return ThreadBase::queue(); }
// Mimics android.view.Choreographer
void postFrameCallback(IFrameCallback* callback);
@@ -140,17 +122,6 @@
void dispatchFrameCallbacks();
void requestVsync();
- // Returns the next task to be run. If this returns NULL nextWakeup is set
- // to the time to requery for the nextTask to run. mNextWakeup is also
- // set to this time
- RenderTask* nextTask(nsecs_t* nextWakeup);
-
- sp<Looper> mLooper;
- Mutex mLock;
-
- nsecs_t mNextWakeup;
- TaskQueue mQueue;
-
DisplayInfo mDisplayInfo;
DisplayEventReceiver* mDisplayEventReceiver;
@@ -162,7 +133,6 @@
// the previous one
std::set<IFrameCallback*> mPendingRegistrationFrameCallbacks;
bool mFrameCallbackTaskPending;
- DispatchFrameCallbacks* mFrameCallbackTask;
TimeLord mTimeLord;
RenderState* mRenderState;
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 64ec58d..e613776 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -19,6 +19,7 @@
#include "hwui/Paint.h"
#include "DeferredLayerUpdater.h"
+#include <minikin/Layout.h>
#include <renderthread/EglManager.h>
#include <renderthread/OpenGLPipeline.h>
#include <pipeline/skia/SkiaOpenGLPipeline.h>
@@ -121,13 +122,15 @@
void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text,
const SkPaint& paint, float x, float y) {
auto utf16 = asciiToUtf16(text);
- canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, 0, paint, nullptr);
+ canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR, paint,
+ nullptr);
}
void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text,
const SkPaint& paint, const SkPath& path) {
auto utf16 = asciiToUtf16(text);
- canvas->drawTextOnPath(utf16.get(), strlen(text), 0, path, 0, 0, paint, nullptr);
+ canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint,
+ nullptr);
}
void TestUtils::TestTask::run() {
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index f293631..c383fcf 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -318,7 +318,9 @@
*/
static void runOnRenderThread(RtCallback rtCallback) {
TestTask task(rtCallback);
- renderthread::RenderThread::getInstance().queueAndWait(&task);
+ renderthread::RenderThread::getInstance().queue().runSync([&]() {
+ task.run();
+ });
}
static bool isRenderThreadRunning() {
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index 5b685bb..21d8d75 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -57,7 +57,7 @@
for (int i = 0; i < 5; i++) {
paint.setTextSize(10 + (frameNr % 20) + i * 20);
canvas->drawText(text.get(), 0, textLength, textLength,
- 0, 100 * (i + 2), minikin::kBidi_Force_LTR, paint, nullptr);
+ 0, 100 * (i + 2), minikin::Bidi::FORCE_LTR, paint, nullptr);
}
container->setStagingDisplayList(canvas->finishRecording());
diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
index ad0b1f1..d678af9 100644
--- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
@@ -55,7 +55,7 @@
std::string offscreen = "offscreen line " + stri;
std::unique_ptr<uint16_t[]> offtext = TestUtils::asciiToUtf16(offscreen.c_str());
canvas.drawText(offtext.get(), 0, offscreen.length(), offscreen.length(),
- bounds.fLeft, top + padding, minikin::kBidi_Force_LTR, mBluePaint, nullptr);
+ bounds.fLeft, top + padding, minikin::Bidi::FORCE_LTR, mBluePaint, nullptr);
canvas.restore();
canvas.drawRect(bounds.fLeft, top + padding, bounds.fRight,
@@ -63,7 +63,7 @@
std::string onscreen = "onscreen line " + stri;
std::unique_ptr<uint16_t[]> ontext = TestUtils::asciiToUtf16(onscreen.c_str());
canvas.drawText(ontext.get(), 0, onscreen.length(), onscreen.length(), bounds.fLeft,
- top + smallRectHeight - padding, minikin::kBidi_Force_LTR, mGreenPaint,
+ top + smallRectHeight - padding, minikin::Bidi::FORCE_LTR, mGreenPaint,
nullptr);
}
}
diff --git a/libs/hwui/tests/microbench/TaskManagerBench.cpp b/libs/hwui/tests/microbench/TaskManagerBench.cpp
index cf47f273..67cb428 100644
--- a/libs/hwui/tests/microbench/TaskManagerBench.cpp
+++ b/libs/hwui/tests/microbench/TaskManagerBench.cpp
@@ -19,7 +19,9 @@
#include "thread/Task.h"
#include "thread/TaskManager.h"
#include "thread/TaskProcessor.h"
+#include "thread/ThreadBase.h"
+#include <atomic>
#include <vector>
using namespace android;
@@ -38,6 +40,8 @@
}
};
+class TestThread : public ThreadBase, public virtual RefBase {};
+
void BM_TaskManager_allocateTask(benchmark::State& state) {
std::vector<sp<TrivialTask> > tasks;
tasks.reserve(state.max_iterations);
@@ -86,3 +90,50 @@
state.PauseTiming();
}
BENCHMARK(BM_TaskManager_enqueueRunDeleteTask);
+
+void BM_Thread_enqueueTask(benchmark::State& state) {
+ sp<TestThread> thread{new TestThread};
+ thread->start();
+
+ atomic_int counter(0);
+ int expected = 0;
+ while (state.KeepRunning()) {
+ expected++;
+ thread->queue().post([&counter](){
+ counter++;
+ });
+ }
+ thread->queue().runSync([](){});
+
+ thread->requestExit();
+ thread->join();
+ if (counter != expected) {
+ printf("Ran %d lambads, should have been %d\n", counter.load(), expected);
+ }
+}
+BENCHMARK(BM_Thread_enqueueTask);
+
+void BM_Thread_enqueueRunDeleteTask(benchmark::State& state) {
+ sp<TestThread> thread{new TestThread};
+ thread->start();
+ std::vector<std::future<int>> tasks;
+ tasks.reserve(state.max_iterations);
+
+ int expected = 0;
+ while (state.KeepRunning()) {
+ tasks.emplace_back(thread->queue().async([expected]() -> int {
+ return expected + 1;
+ }));
+ expected++;
+ }
+ state.ResumeTiming();
+ expected = 0;
+ for (auto& future : tasks) {
+ if (future.get() != ++expected) {
+ printf("Mismatch expected %d vs. observed %d\n", expected, future.get());
+ }
+ }
+ tasks.clear();
+ state.PauseTiming();
+}
+BENCHMARK(BM_Thread_enqueueRunDeleteTask);
\ No newline at end of file
diff --git a/libs/hwui/tests/scripts/prep_taieye.sh b/libs/hwui/tests/scripts/prep_taieye.sh
new file mode 100755
index 0000000..503f6d5
--- /dev/null
+++ b/libs/hwui/tests/scripts/prep_taieye.sh
@@ -0,0 +1,50 @@
+nr=$(adb shell cat /proc/cpuinfo | grep processor | wc -l)
+cpubase=/sys/devices/system/cpu
+
+adb root
+adb wait-for-device
+adb shell stop vendor.perfd
+adb shell stop thermal-engine
+
+S=1036800
+cpu=0
+# Changing governor and frequency in one core will be automatically applied
+# to other cores in the cluster
+while [ $((cpu < 4)) -eq 1 ]; do
+ echo "Setting cpu ${cpu} to $S hz"
+ adb shell "echo userspace > $cpubase/cpu${cpu}/cpufreq/scaling_governor"
+ adb shell "echo 1 > $cpubase/cpu${cpu}/online"
+ adb shell "echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_max_freq"
+ adb shell "echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_min_freq"
+ cpu=$(($cpu + 1))
+done
+
+while [ $((cpu < $nr)) -eq 1 ]; do
+ echo "disable cpu $cpu"
+ adb shell "echo 0 > $cpubase/cpu${cpu}/online"
+ cpu=$(($cpu + 1))
+done
+
+echo "setting GPU bus and idle timer"
+adb shell "echo 0 > /sys/class/kgsl/kgsl-3d0/bus_split"
+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 3143 4173 5195 5859 7759 9887 11863 13763
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,gpubw/min_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,gpubw/max_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,cpubw/min_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,cpubw/max_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,mincpubw/min_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,mincpubw/max_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,memlat-cpu0/min_freq"
+adb shell "echo 7759 > /sys/class/devfreq/soc\:qcom,memlat-cpu0/max_freq"
+
+# 180000000 257000000 342000000 414000000 515000000 596000000 670000000 710000000
+echo "performance mode, 342 MHz"
+adb shell "echo performance > /sys/class/kgsl/kgsl-3d0/devfreq/governor"
+adb shell "echo 342000000 > /sys/class/kgsl/kgsl-3d0/devfreq/min_freq"
+adb shell "echo 342000000 > /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/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index c4e4195..dab1f89 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -806,7 +806,7 @@
paint.setTextSize(20);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
- canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::kBidi_Force_LTR, paint, NULL);
+ canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL);
});
int count = 0;
@@ -830,7 +830,7 @@
paint.setTextSize(20);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
- canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::kBidi_Force_LTR, paint, NULL);
+ canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL);
});
Properties::enableHighContrastText = false;
diff --git a/libs/hwui/tests/unit/ThreadBaseTests.cpp b/libs/hwui/tests/unit/ThreadBaseTests.cpp
new file mode 100644
index 0000000..7aad348
--- /dev/null
+++ b/libs/hwui/tests/unit/ThreadBaseTests.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "thread/ThreadBase.h"
+#include "utils/TimeUtils.h"
+
+#include <chrono>
+#include "unistd.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+static ThreadBase& thread() {
+ class TestThread : public ThreadBase, public virtual RefBase {};
+ static sp<TestThread> thread = []() -> auto {
+ sp<TestThread> ret{new TestThread};
+ ret->start("TestThread");
+ return ret;
+ }();
+ return *thread;
+}
+
+static WorkQueue& queue() {
+ return thread().queue();
+}
+
+TEST(ThreadBase, post) {
+ std::atomic_bool ran(false);
+ queue().post([&ran]() {
+ ran = true;
+ });
+ for (int i = 0; !ran && i < 1000; i++) {
+ usleep(1);
+ }
+ ASSERT_TRUE(ran) << "Failed to flip atomic after 1 second";
+}
+
+TEST(ThreadBase, postDelay) {
+ using clock = WorkQueue::clock;
+
+ std::promise<nsecs_t> ranAtPromise;
+ auto queuedAt = clock::now();
+ queue().postDelayed(100_us, [&]() {
+ ranAtPromise.set_value(clock::now());
+ });
+ auto ranAt = ranAtPromise.get_future().get();
+ auto ranAfter = ranAt - queuedAt;
+ ASSERT_TRUE(ranAfter > 90_us) << "Ran after " << ns2us(ranAfter) << "us <= 90us";
+}
+
+TEST(ThreadBase, runSync) {
+ pid_t thisTid = gettid();
+ pid_t otherTid = thisTid;
+
+ auto result = queue().runSync([&otherTid]() -> auto {
+ otherTid = gettid();
+ return 42;
+ });
+
+ ASSERT_EQ(42, result);
+ ASSERT_NE(thisTid, otherTid);
+}
+
+TEST(ThreadBase, async) {
+ pid_t thisTid = gettid();
+ pid_t thisPid = getpid();
+
+ auto otherTid = queue().async([]() -> auto {
+ return gettid();
+ });
+ auto otherPid = queue().async([]() -> auto {
+ return getpid();
+ });
+ auto result = queue().async([]() -> auto {
+ return 42;
+ });
+
+ ASSERT_NE(thisTid, otherTid.get());
+ ASSERT_EQ(thisPid, otherPid.get());
+ ASSERT_EQ(42, result.get());
+}
+
+TEST(ThreadBase, lifecyclePerf) {
+ struct EventCount {
+ std::atomic_int construct{0};
+ std::atomic_int destruct{0};
+ std::atomic_int copy{0};
+ std::atomic_int move{0};
+ };
+
+ struct Counter {
+ Counter(EventCount* count) : mCount(count) {
+ mCount->construct++;
+ }
+
+ Counter(const Counter& other) : mCount(other.mCount) {
+ if (mCount) mCount->copy++;
+ }
+
+ Counter(Counter&& other) : mCount(other.mCount) {
+ other.mCount = nullptr;
+ if (mCount) mCount->move++;
+ }
+
+ Counter& operator=(const Counter& other) {
+ mCount = other.mCount;
+ if (mCount) mCount->copy++;
+ return *this;
+ }
+
+ Counter& operator=(Counter&& other) {
+ mCount = other.mCount;
+ other.mCount = nullptr;
+ if (mCount) mCount->move++;
+ return *this;
+ }
+
+ ~Counter() {
+ if (mCount) mCount->destruct++;
+ }
+
+ EventCount* mCount;
+ };
+
+ EventCount count;
+ {
+ Counter counter{&count};
+ queue().runSync([c = std::move(counter)](){});
+ }
+ ASSERT_EQ(1, count.construct.load());
+ ASSERT_EQ(1, count.destruct.load());
+ ASSERT_EQ(0, count.copy.load());
+ ASSERT_LE(1, count.move.load());
+}
+
+int lifecycleTestHelper(const sp<VirtualLightRefBase>& test) {
+ return queue().runSync([t = test]() -> int {
+ return t->getStrongCount();
+ });
+}
+
+TEST(ThreadBase, lifecycle) {
+ sp<VirtualLightRefBase> dummyObject{new VirtualLightRefBase};
+ ASSERT_EQ(1, dummyObject->getStrongCount());
+ ASSERT_EQ(2, queue().runSync([dummyObject]() -> int {
+ return dummyObject->getStrongCount();
+ }));
+ ASSERT_EQ(1, dummyObject->getStrongCount());
+ ASSERT_EQ(2, lifecycleTestHelper(dummyObject));
+ ASSERT_EQ(1, dummyObject->getStrongCount());
+}
\ No newline at end of file
diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/thread/ThreadBase.h
new file mode 100644
index 0000000..402fd1e
--- /dev/null
+++ b/libs/hwui/thread/ThreadBase.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HWUI_THREADBASE_H
+#define HWUI_THREADBASE_H
+
+#include "WorkQueue.h"
+#include "utils/Macros.h"
+
+#include <utils/Looper.h>
+#include <utils/Thread.h>
+
+#include <algorithm>
+
+namespace android::uirenderer {
+
+class ThreadBase : protected Thread {
+ PREVENT_COPY_AND_ASSIGN(ThreadBase);
+
+public:
+ ThreadBase()
+ : mLooper(new Looper(false))
+ , mQueue([this](){ mLooper->wake(); }, mLock)
+ {}
+
+ WorkQueue& queue() { return mQueue; }
+
+ void requestExit() {
+ Thread::requestExit();
+ mLooper->wake();
+ }
+
+ void start(const char* name = "ThreadBase") {
+ Thread::run(name);
+ }
+
+ void join() {
+ Thread::join();
+ }
+
+protected:
+ void waitForWork() {
+ nsecs_t nextWakeup;
+ {
+ std::unique_lock lock{mLock};
+ nextWakeup = mQueue.nextWakeup(lock);
+ }
+ int timeout = -1;
+ if (nextWakeup < std::numeric_limits<nsecs_t>::max()) {
+ timeout = ns2ms(nextWakeup - WorkQueue::clock::now());
+ if (timeout < 0) timeout = 0;
+ }
+ int result = mLooper->pollOnce(timeout);
+ LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
+ "RenderThread Looper POLL_ERROR!");
+ }
+
+ void processQueue() {
+ mQueue.process();
+ }
+
+ virtual bool threadLoop() override {
+ while (!exitPending()) {
+ waitForWork();
+ processQueue();
+ }
+ return false;
+ }
+
+ sp<Looper> mLooper;
+
+private:
+ WorkQueue mQueue;
+ std::mutex mLock;
+};
+
+} // namespace android::uirenderer
+
+
+#endif //HWUI_THREADBASE_H
diff --git a/libs/hwui/thread/WorkQueue.h b/libs/hwui/thread/WorkQueue.h
new file mode 100644
index 0000000..fbb24bb
--- /dev/null
+++ b/libs/hwui/thread/WorkQueue.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HWUI_WORKQUEUE_H
+#define HWUI_WORKQUEUE_H
+
+#include "utils/Macros.h"
+
+#include <log/log.h>
+#include <utils/Timers.h>
+
+#include <condition_variable>
+#include <functional>
+#include <future>
+#include <mutex>
+#include <variant>
+#include <vector>
+
+namespace android::uirenderer {
+
+struct MonotonicClock {
+ static nsecs_t now() { return systemTime(CLOCK_MONOTONIC); }
+};
+
+class WorkQueue {
+ PREVENT_COPY_AND_ASSIGN(WorkQueue);
+public:
+ using clock = MonotonicClock;
+
+private:
+ struct WorkItem {
+ WorkItem() = delete;
+ WorkItem(const WorkItem& other) = delete;
+ WorkItem& operator=(const WorkItem& other) = delete;
+ WorkItem(WorkItem&& other) = default;
+ WorkItem& operator=(WorkItem&& other) = default;
+
+ WorkItem(nsecs_t runAt, std::function<void()>&& work)
+ : runAt(runAt), work(std::move(work)) {}
+
+ nsecs_t runAt;
+ std::function<void()> work;
+ };
+
+public:
+ WorkQueue(std::function<void()>&& wakeFunc, std::mutex& lock)
+ : mWakeFunc(move(wakeFunc))
+ , mLock(lock) {}
+
+ void process() {
+ auto now = clock::now();
+ std::vector<WorkItem> toProcess;
+ {
+ std::unique_lock _lock{mLock};
+ if (mWorkQueue.empty()) return;
+ toProcess = std::move(mWorkQueue);
+ auto moveBack = find_if(std::begin(toProcess), std::end(toProcess),
+ [&now](WorkItem& item) {
+ return item.runAt > now;
+ });
+ if (moveBack != std::end(toProcess)) {
+ mWorkQueue.reserve(std::distance(moveBack, std::end(toProcess)) + 5);
+ std::move(moveBack, std::end(toProcess), std::back_inserter(mWorkQueue));
+ toProcess.erase(moveBack, std::end(toProcess));
+ }
+ }
+ for (auto& item : toProcess) {
+ item.work();
+ }
+ }
+
+ template<class F>
+ void postAt(nsecs_t time, F&& func) {
+ enqueue(WorkItem{time, std::function<void()>(std::forward<F>(func))});
+ }
+
+ template<class F>
+ void postDelayed(nsecs_t delay, F&& func) {
+ enqueue(WorkItem{clock::now() + delay, std::function<void()>(std::forward<F>(func))});
+ }
+
+ template<class F>
+ void post(F&& func) {
+ postAt(0, std::forward<F>(func));
+ }
+
+ template<class F>
+ auto async(F&& func) -> std::future<decltype(func())> {
+ typedef std::packaged_task<decltype(func())()> task_t;
+ auto task = std::make_shared<task_t>(std::forward<F>(func));
+ post([task]() { std::invoke(*task); });
+ return task->get_future();
+ }
+
+ template<class F>
+ auto runSync(F&& func) -> decltype(func()) {
+ std::packaged_task<decltype(func())()> task{std::forward<F>(func)};
+ post([&task]() { std::invoke(task); });
+ return task.get_future().get();
+ };
+
+ nsecs_t nextWakeup(std::unique_lock<std::mutex> &lock) {
+ if (mWorkQueue.empty()) {
+ return std::numeric_limits<nsecs_t>::max();
+ } else {
+ return std::begin(mWorkQueue)->runAt;
+ }
+ }
+
+private:
+ void enqueue(WorkItem&& item) {
+ bool needsWakeup;
+ {
+ std::unique_lock _lock{mLock};
+ auto insertAt = std::find_if(std::begin(mWorkQueue), std::end(mWorkQueue),
+ [time = item.runAt](WorkItem& item) {
+ return item.runAt > time;
+ });
+ needsWakeup = std::begin(mWorkQueue) == insertAt;
+ mWorkQueue.emplace(insertAt, std::move(item));
+ }
+ if (needsWakeup) {
+ mWakeFunc();
+ }
+ }
+
+ std::function<void()> mWakeFunc;
+
+ std::mutex& mLock;
+ std::vector<WorkItem> mWorkQueue;
+};
+
+} // namespace android::uirenderer
+
+#endif //HWUI_WORKQUEUE_H
diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp
new file mode 100644
index 0000000..4f1d2d5
--- /dev/null
+++ b/libs/protoutil/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+ name: "libprotoutil",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-unused-variable",
+ "-Wunused-parameter",
+ ],
+
+ srcs: [
+ "src/EncodedBuffer.cpp",
+ "src/ProtoOutputStream.cpp",
+ "src/protobuf.cpp",
+ ],
+
+ export_include_dirs: ["include"],
+
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ ],
+}
diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h
index 0f1cced..ce41849 100644
--- a/libs/protoutil/include/android/util/ProtoOutputStream.h
+++ b/libs/protoutil/include/android/util/ProtoOutputStream.h
@@ -19,13 +19,63 @@
#include <android/util/EncodedBuffer.h>
-#include <stdint.h>
#include <string>
namespace android {
namespace util {
/**
+ * Position of the field type in a (long long) fieldId.
+ */
+const uint64_t FIELD_TYPE_SHIFT = 32;
+
+/**
+ * Mask for the field types stored in a fieldId. Leaves a whole
+ * byte for future expansion, even though there are currently only 17 types.
+ */
+const uint64_t FIELD_TYPE_MASK = 0x0ffULL << FIELD_TYPE_SHIFT;
+
+/**
+ * The types are copied from external/protobuf/src/google/protobuf/descriptor.h directly,
+ * so no extra mapping needs to be maintained in this case.
+ */
+const uint64_t FIELD_TYPE_UNKNOWN = 0;
+const uint64_t FIELD_TYPE_DOUBLE = 1ULL << FIELD_TYPE_SHIFT; // double, exactly eight bytes on the wire.
+const uint64_t FIELD_TYPE_FLOAT = 2ULL << FIELD_TYPE_SHIFT; // float, exactly four bytes on the wire.
+const uint64_t FIELD_TYPE_INT64 = 3ULL << FIELD_TYPE_SHIFT; // int64, varint on the wire. Negative numbers
+ // take 10 bytes. Use TYPE_SINT64 if negative
+ // values are likely.
+const uint64_t FIELD_TYPE_UINT64 = 4ULL << FIELD_TYPE_SHIFT; // uint64, varint on the wire.
+const uint64_t FIELD_TYPE_INT32 = 5ULL << FIELD_TYPE_SHIFT; // int32, varint on the wire. Negative numbers
+ // take 10 bytes. Use TYPE_SINT32 if negative
+ // values are likely.
+const uint64_t FIELD_TYPE_FIXED64 = 6ULL << FIELD_TYPE_SHIFT; // uint64, exactly eight bytes on the wire.
+const uint64_t FIELD_TYPE_FIXED32 = 7ULL << FIELD_TYPE_SHIFT; // uint32, exactly four bytes on the wire.
+const uint64_t FIELD_TYPE_BOOL = 8ULL << FIELD_TYPE_SHIFT; // bool, varint on the wire.
+const uint64_t FIELD_TYPE_STRING = 9ULL << FIELD_TYPE_SHIFT; // UTF-8 text.
+// const uint64_t FIELD_TYPE_GROUP = 10ULL << FIELD_TYPE_SHIFT; // Tag-delimited message. Deprecated.
+const uint64_t FIELD_TYPE_MESSAGE = 11ULL << FIELD_TYPE_SHIFT; // Length-delimited message.
+
+const uint64_t FIELD_TYPE_BYTES = 12ULL << FIELD_TYPE_SHIFT; // Arbitrary byte array.
+const uint64_t FIELD_TYPE_UINT32 = 13ULL << FIELD_TYPE_SHIFT; // uint32, varint on the wire
+const uint64_t FIELD_TYPE_ENUM = 14ULL << FIELD_TYPE_SHIFT; // Enum, varint on the wire
+const uint64_t FIELD_TYPE_SFIXED32 = 15ULL << FIELD_TYPE_SHIFT; // int32, exactly four bytes on the wire
+const uint64_t FIELD_TYPE_SFIXED64 = 16ULL << FIELD_TYPE_SHIFT; // int64, exactly eight bytes on the wire
+const uint64_t FIELD_TYPE_SINT32 = 17ULL << FIELD_TYPE_SHIFT; // int32, ZigZag-encoded varint on the wire
+const uint64_t FIELD_TYPE_SINT64 = 18ULL << FIELD_TYPE_SHIFT; // int64, ZigZag-encoded varint on the wire
+
+//
+// FieldId flags for whether the field is single, repeated or packed.
+// TODO: packed is not supported yet.
+//
+const uint64_t FIELD_COUNT_SHIFT = 40;
+const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_UNKNOWN = 0;
+const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_PACKED = 5ULL << FIELD_COUNT_SHIFT;
+
+/**
* Class to write to a protobuf stream.
*
* Each write method takes an ID code from the protoc generated classes
@@ -49,7 +99,7 @@
bool write(uint64_t fieldId, long long val);
bool write(uint64_t fieldId, bool val);
bool write(uint64_t fieldId, std::string val);
- bool write(uint64_t fieldId, const char* val);
+ bool write(uint64_t fieldId, const char* val, size_t size);
/**
* Starts a sub-message write session.
@@ -60,13 +110,14 @@
void end(long long token);
/**
- * Flushes the protobuf data out to given fd.
+ * Flushes the protobuf data out to given fd. When the following functions are called,
+ * it is not able to write to ProtoOutputStream any more since the data is compact.
*/
- size_t size();
- EncodedBuffer::iterator data();
- bool flush(int fd);
+ size_t size(); // Get the size of the serialized protobuf.
+ EncodedBuffer::iterator data(); // Get the reader apis of the data.
+ bool flush(int fd); // Flush data directly to a file descriptor.
- // Please don't use the following functions to dump protos unless you are sure about it.
+ // Please don't use the following functions to dump protos unless you are familiar with protobuf encoding.
void writeRawVarint(uint64_t varint);
void writeLengthDelimitedHeader(uint32_t id, size_t size);
void writeRawByte(uint8_t byte);
@@ -94,6 +145,7 @@
inline void writeEnumImpl(uint32_t id, int val);
inline void writeBoolImpl(uint32_t id, bool val);
inline void writeUtf8StringImpl(uint32_t id, const char* val, size_t size);
+ inline void writeMessageBytesImpl(uint32_t id, const char* val, size_t size);
bool compact();
size_t editEncodedSize(size_t rawSize);
@@ -103,4 +155,4 @@
}
}
-#endif // ANDROID_UTIL_PROTOOUTPUT_STREAM_H
\ No newline at end of file
+#endif // ANDROID_UTIL_PROTOOUTPUT_STREAM_H
diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp
index 15144ac..9d8ee729 100644
--- a/libs/protoutil/src/ProtoOutputStream.cpp
+++ b/libs/protoutil/src/ProtoOutputStream.cpp
@@ -18,58 +18,10 @@
#include <android/util/protobuf.h>
#include <android/util/ProtoOutputStream.h>
#include <cutils/log.h>
-#include <cstring>
namespace android {
namespace util {
-/**
- * Position of the field type in a (long long) fieldId.
- */
-const uint64_t FIELD_TYPE_SHIFT = 32;
-
-/**
- * Mask for the field types stored in a fieldId. Leaves a whole
- * byte for future expansion, even though there are currently only 17 types.
- */
-const uint64_t FIELD_TYPE_MASK = 0x0ffULL << FIELD_TYPE_SHIFT;
-
-const uint64_t FIELD_TYPE_UNKNOWN = 0;
-const uint64_t TYPE_DOUBLE = 1ULL << FIELD_TYPE_SHIFT; // double, exactly eight bytes on the wire.
-const uint64_t TYPE_FLOAT = 2ULL << FIELD_TYPE_SHIFT; // float, exactly four bytes on the wire.
-const uint64_t TYPE_INT64 = 3ULL << FIELD_TYPE_SHIFT; // int64, varint on the wire. Negative numbers
- // take 10 bytes. Use TYPE_SINT64 if negative
- // values are likely.
-const uint64_t TYPE_UINT64 = 4ULL << FIELD_TYPE_SHIFT; // uint64, varint on the wire.
-const uint64_t TYPE_INT32 = 5ULL << FIELD_TYPE_SHIFT; // int32, varint on the wire. Negative numbers
- // take 10 bytes. Use TYPE_SINT32 if negative
- // values are likely.
-const uint64_t TYPE_FIXED64 = 6ULL << FIELD_TYPE_SHIFT; // uint64, exactly eight bytes on the wire.
-const uint64_t TYPE_FIXED32 = 7ULL << FIELD_TYPE_SHIFT; // uint32, exactly four bytes on the wire.
-const uint64_t TYPE_BOOL = 8ULL << FIELD_TYPE_SHIFT; // bool, varint on the wire.
-const uint64_t TYPE_STRING = 9ULL << FIELD_TYPE_SHIFT; // UTF-8 text.
-const uint64_t TYPE_GROUP = 10ULL << FIELD_TYPE_SHIFT; // Tag-delimited message. Deprecated.
-const uint64_t TYPE_MESSAGE = 11ULL << FIELD_TYPE_SHIFT; // Length-delimited message.
-
-const uint64_t TYPE_BYTES = 12ULL << FIELD_TYPE_SHIFT; // Arbitrary byte array.
-const uint64_t TYPE_UINT32 = 13ULL << FIELD_TYPE_SHIFT; // uint32, varint on the wire
-const uint64_t TYPE_ENUM = 14ULL << FIELD_TYPE_SHIFT; // Enum, varint on the wire
-const uint64_t TYPE_SFIXED32 = 15ULL << FIELD_TYPE_SHIFT; // int32, exactly four bytes on the wire
-const uint64_t TYPE_SFIXED64 = 16ULL << FIELD_TYPE_SHIFT; // int64, exactly eight bytes on the wire
-const uint64_t TYPE_SINT32 = 17ULL << FIELD_TYPE_SHIFT; // int32, ZigZag-encoded varint on the wire
-const uint64_t TYPE_SINT64 = 18ULL << FIELD_TYPE_SHIFT; // int64, ZigZag-encoded varint on the wire
-
-//
-// FieldId flags for whether the field is single, repeated or packed.
-// TODO: packed is not supported yet.
-//
-const uint64_t FIELD_COUNT_SHIFT = 40;
-const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_UNKNOWN = 0;
-const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_PACKED = 4ULL << FIELD_COUNT_SHIFT;
-
ProtoOutputStream::ProtoOutputStream()
:mBuffer(),
mCopyBegin(0),
@@ -90,18 +42,18 @@
if (mCompact) return false;
const uint32_t id = (uint32_t)fieldId;
switch (fieldId & FIELD_TYPE_MASK) {
- case TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break;
- case TYPE_FLOAT: writeFloatImpl(id, (float)val); break;
- case TYPE_INT64: writeInt64Impl(id, (long long)val); break;
- case TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break;
- case TYPE_INT32: writeInt32Impl(id, (int)val); break;
- case TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break;
- case TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break;
- case TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break;
- case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break;
- case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break;
- case TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break;
- case TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break;
+ case FIELD_TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break;
+ case FIELD_TYPE_FLOAT: writeFloatImpl(id, (float)val); break;
+ case FIELD_TYPE_INT64: writeInt64Impl(id, (long long)val); break;
+ case FIELD_TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break;
+ case FIELD_TYPE_INT32: writeInt32Impl(id, (int)val); break;
+ case FIELD_TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break;
+ case FIELD_TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break;
+ case FIELD_TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break;
+ case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break;
+ case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break;
+ case FIELD_TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break;
+ case FIELD_TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break;
default:
ALOGW("Field type %d is not supported when writing double val.",
(int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -116,18 +68,18 @@
if (mCompact) return false;
const uint32_t id = (uint32_t)fieldId;
switch (fieldId & FIELD_TYPE_MASK) {
- case TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break;
- case TYPE_FLOAT: writeFloatImpl(id, (float)val); break;
- case TYPE_INT64: writeInt64Impl(id, (long long)val); break;
- case TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break;
- case TYPE_INT32: writeInt32Impl(id, (int)val); break;
- case TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break;
- case TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break;
- case TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break;
- case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break;
- case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break;
- case TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break;
- case TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break;
+ case FIELD_TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break;
+ case FIELD_TYPE_FLOAT: writeFloatImpl(id, (float)val); break;
+ case FIELD_TYPE_INT64: writeInt64Impl(id, (long long)val); break;
+ case FIELD_TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break;
+ case FIELD_TYPE_INT32: writeInt32Impl(id, (int)val); break;
+ case FIELD_TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break;
+ case FIELD_TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break;
+ case FIELD_TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break;
+ case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break;
+ case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break;
+ case FIELD_TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break;
+ case FIELD_TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break;
default:
ALOGW("Field type %d is not supported when writing float val.",
(int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -142,20 +94,20 @@
if (mCompact) return false;
const uint32_t id = (uint32_t)fieldId;
switch (fieldId & FIELD_TYPE_MASK) {
- case TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break;
- case TYPE_FLOAT: writeFloatImpl(id, (float)val); break;
- case TYPE_INT64: writeInt64Impl(id, (long long)val); break;
- case TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break;
- case TYPE_INT32: writeInt32Impl(id, (int)val); break;
- case TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break;
- case TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break;
- case TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break;
- case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break;
- case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break;
- case TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break;
- case TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break;
- case TYPE_ENUM: writeEnumImpl(id, (int)val); break;
- case TYPE_BOOL: writeBoolImpl(id, val != 0); break;
+ case FIELD_TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break;
+ case FIELD_TYPE_FLOAT: writeFloatImpl(id, (float)val); break;
+ case FIELD_TYPE_INT64: writeInt64Impl(id, (long long)val); break;
+ case FIELD_TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break;
+ case FIELD_TYPE_INT32: writeInt32Impl(id, (int)val); break;
+ case FIELD_TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break;
+ case FIELD_TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break;
+ case FIELD_TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break;
+ case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break;
+ case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break;
+ case FIELD_TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break;
+ case FIELD_TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break;
+ case FIELD_TYPE_ENUM: writeEnumImpl(id, (int)val); break;
+ case FIELD_TYPE_BOOL: writeBoolImpl(id, val != 0); break;
default:
ALOGW("Field type %d is not supported when writing int val.",
(int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -170,20 +122,20 @@
if (mCompact) return false;
const uint32_t id = (uint32_t)fieldId;
switch (fieldId & FIELD_TYPE_MASK) {
- case TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break;
- case TYPE_FLOAT: writeFloatImpl(id, (float)val); break;
- case TYPE_INT64: writeInt64Impl(id, (long long)val); break;
- case TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break;
- case TYPE_INT32: writeInt32Impl(id, (int)val); break;
- case TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break;
- case TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break;
- case TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break;
- case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break;
- case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break;
- case TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break;
- case TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break;
- case TYPE_ENUM: writeEnumImpl(id, (int)val); break;
- case TYPE_BOOL: writeBoolImpl(id, val != 0); break;
+ case FIELD_TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break;
+ case FIELD_TYPE_FLOAT: writeFloatImpl(id, (float)val); break;
+ case FIELD_TYPE_INT64: writeInt64Impl(id, (long long)val); break;
+ case FIELD_TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break;
+ case FIELD_TYPE_INT32: writeInt32Impl(id, (int)val); break;
+ case FIELD_TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break;
+ case FIELD_TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break;
+ case FIELD_TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break;
+ case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break;
+ case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break;
+ case FIELD_TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break;
+ case FIELD_TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break;
+ case FIELD_TYPE_ENUM: writeEnumImpl(id, (int)val); break;
+ case FIELD_TYPE_BOOL: writeBoolImpl(id, val != 0); break;
default:
ALOGW("Field type %d is not supported when writing long long val.",
(int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -198,7 +150,7 @@
if (mCompact) return false;
const uint32_t id = (uint32_t)fieldId;
switch (fieldId & FIELD_TYPE_MASK) {
- case TYPE_BOOL:
+ case FIELD_TYPE_BOOL:
writeBoolImpl(id, val);
return true;
default:
@@ -214,7 +166,7 @@
if (mCompact) return false;
const uint32_t id = (uint32_t)fieldId;
switch (fieldId & FIELD_TYPE_MASK) {
- case TYPE_STRING:
+ case FIELD_TYPE_STRING:
writeUtf8StringImpl(id, val.c_str(), val.size());
return true;
default:
@@ -225,16 +177,19 @@
}
bool
-ProtoOutputStream::write(uint64_t fieldId, const char* val)
+ProtoOutputStream::write(uint64_t fieldId, const char* val, size_t size)
{
if (mCompact) return false;
const uint32_t id = (uint32_t)fieldId;
- int size = 0;
- while (val[size] != '\0') size++;
switch (fieldId & FIELD_TYPE_MASK) {
- case TYPE_STRING:
+ case FIELD_TYPE_STRING:
+ case FIELD_TYPE_BYTES:
writeUtf8StringImpl(id, val, size);
return true;
+ case FIELD_TYPE_MESSAGE:
+ // can directly write valid format of message bytes into ProtoOutputStream without calling start/end
+ writeMessageBytesImpl(id, val, size);
+ return true;
default:
ALOGW("Field type %d is not supported when writing char[] val.",
(int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
@@ -288,7 +243,7 @@
long long
ProtoOutputStream::start(uint64_t fieldId)
{
- if ((fieldId & FIELD_TYPE_MASK) != TYPE_MESSAGE) {
+ if ((fieldId & FIELD_TYPE_MASK) != FIELD_TYPE_MESSAGE) {
ALOGE("Can't call start for non-message type field: 0x%llx", (long long)fieldId);
return 0;
}
@@ -561,7 +516,6 @@
inline void
ProtoOutputStream::writeDoubleImpl(uint32_t id, double val)
{
- if (val == 0.0) return;
mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
mBuffer.writeRawFixed64(bit_cast<double, uint64_t>(val));
}
@@ -569,7 +523,6 @@
inline void
ProtoOutputStream::writeFloatImpl(uint32_t id, float val)
{
- if (val == 0.0) return;
mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
mBuffer.writeRawFixed32(bit_cast<float, uint32_t>(val));
}
@@ -577,7 +530,6 @@
inline void
ProtoOutputStream::writeInt64Impl(uint32_t id, long long val)
{
- if (val == 0) return;
mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint64((uint64_t)val);
}
@@ -585,7 +537,6 @@
inline void
ProtoOutputStream::writeInt32Impl(uint32_t id, int val)
{
- if (val == 0) return;
mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint32((uint32_t)val);
}
@@ -593,7 +544,6 @@
inline void
ProtoOutputStream::writeUint64Impl(uint32_t id, uint64_t val)
{
- if (val == 0) return;
mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint64(val);
}
@@ -601,7 +551,6 @@
inline void
ProtoOutputStream::writeUint32Impl(uint32_t id, uint32_t val)
{
- if (val == 0) return;
mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint32(val);
}
@@ -609,7 +558,6 @@
inline void
ProtoOutputStream::writeFixed64Impl(uint32_t id, uint64_t val)
{
- if (val == 0) return;
mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
mBuffer.writeRawFixed64(val);
}
@@ -617,7 +565,6 @@
inline void
ProtoOutputStream::writeFixed32Impl(uint32_t id, uint32_t val)
{
- if (val == 0) return;
mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
mBuffer.writeRawFixed32(val);
}
@@ -625,7 +572,6 @@
inline void
ProtoOutputStream::writeSFixed64Impl(uint32_t id, long long val)
{
- if (val == 0) return;
mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
mBuffer.writeRawFixed64((uint64_t)val);
}
@@ -633,7 +579,6 @@
inline void
ProtoOutputStream::writeSFixed32Impl(uint32_t id, int val)
{
- if (val == 0) return;
mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
mBuffer.writeRawFixed32((uint32_t)val);
}
@@ -641,7 +586,6 @@
inline void
ProtoOutputStream::writeZigzagInt64Impl(uint32_t id, long long val)
{
- if (val == 0) return;
mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint64((val << 1) ^ (val >> 63));
}
@@ -649,7 +593,6 @@
inline void
ProtoOutputStream::writeZigzagInt32Impl(uint32_t id, int val)
{
- if (val == 0) return;
mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint32((val << 1) ^ (val >> 31));
}
@@ -664,7 +607,6 @@
inline void
ProtoOutputStream::writeBoolImpl(uint32_t id, bool val)
{
- if (!val) return;
mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint32(val ? 1 : 0);
}
@@ -672,13 +614,23 @@
inline void
ProtoOutputStream::writeUtf8StringImpl(uint32_t id, const char* val, size_t size)
{
- if (val == NULL || size == 0) return;
+ if (val == NULL) return;
writeLengthDelimitedHeader(id, size);
for (size_t i=0; i<size; i++) {
mBuffer.writeRawByte((uint8_t)val[i]);
}
}
+inline void
+ProtoOutputStream::writeMessageBytesImpl(uint32_t id, const char* val, size_t size)
+{
+ if (val == NULL) return;
+ writeLengthDelimitedHeader(id, size);
+ for (size_t i=0; i<size; i++) {
+ mBuffer.writeRawByte(val[i]);
+ }
+}
+
} // util
} // android
diff --git a/location/java/android/location/GnssClock.java b/location/java/android/location/GnssClock.java
index 25d247d..671c57c 100644
--- a/location/java/android/location/GnssClock.java
+++ b/location/java/android/location/GnssClock.java
@@ -332,6 +332,9 @@
/**
* Gets the clock's Drift in nanoseconds per second.
*
+ * <p>This value is the instantaneous time-derivative of the value provided by
+ * {@link #getBiasNanos()}.
+ *
* <p>A positive value indicates that the frequency is higher than the nominal (e.g. GPS master
* clock) frequency. The error estimate for this reported drift is
* {@link #getDriftUncertaintyNanosPerSecond()}.
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index e7f903e..c9d2f7f 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -821,7 +821,7 @@
* considered 1 standard deviation.
*
* <p>For example, if {@link #getAltitude()} returns 150, and
- * {@link #getVerticalAccuracyMeters()} ()} returns 20 then there is a 68% probability
+ * {@link #getVerticalAccuracyMeters()} returns 20 then there is a 68% probability
* of the true altitude being between 130 and 170 meters.
*
* <p>If this location does not have a vertical accuracy, then 0.0 is returned.
@@ -933,7 +933,7 @@
* considered 1 standard deviation.
*
* <p>For example, if {@link #getBearing()} returns 60, and
- * {@link #getBearingAccuracyDegrees()} ()} returns 10, then there is a 68% probability of the
+ * {@link #getBearingAccuracyDegrees()} returns 10, then there is a 68% probability of the
* true bearing being between 50 and 70 degrees.
*
* <p>If this location does not have a bearing accuracy, then 0.0 is returned.
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index ba41a7b..9175416 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2564,51 +2564,66 @@
});
}
+ String hasImage = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
String hasVideo = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
- final String METADATA_HAS_VIDEO_VALUE_YES = "yes";
- if (METADATA_HAS_VIDEO_VALUE_YES.equals(hasVideo)) {
- String width = retriever.extractMetadata(
+ String width = null;
+ String height = null;
+ String rotation = null;
+ final String METADATA_VALUE_YES = "yes";
+ // If the file has both image and video, prefer image info over video info.
+ // App querying ExifInterface is most likely using the bitmap path which
+ // picks the image first.
+ if (METADATA_VALUE_YES.equals(hasImage)) {
+ width = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH);
+ height = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT);
+ rotation = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION);
+ } else if (METADATA_VALUE_YES.equals(hasVideo)) {
+ width = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
- String height = retriever.extractMetadata(
+ height = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
-
- if (width != null) {
- mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
- ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
- }
-
- if (height != null) {
- mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
- ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
- }
-
- String rotation = retriever.extractMetadata(
+ rotation = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
- if (rotation != null) {
- int orientation = ExifInterface.ORIENTATION_NORMAL;
+ }
- // all rotation angles in CW
- switch (Integer.parseInt(rotation)) {
- case 90:
- orientation = ExifInterface.ORIENTATION_ROTATE_90;
- break;
- case 180:
- orientation = ExifInterface.ORIENTATION_ROTATE_180;
- break;
- case 270:
- orientation = ExifInterface.ORIENTATION_ROTATE_270;
- break;
- }
+ if (width != null) {
+ mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
+ ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
+ }
- mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
- ExifAttribute.createUShort(orientation, mExifByteOrder));
+ if (height != null) {
+ mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
+ ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
+ }
+
+ if (rotation != null) {
+ int orientation = ExifInterface.ORIENTATION_NORMAL;
+
+ // all rotation angles in CW
+ switch (Integer.parseInt(rotation)) {
+ case 90:
+ orientation = ExifInterface.ORIENTATION_ROTATE_90;
+ break;
+ case 180:
+ orientation = ExifInterface.ORIENTATION_ROTATE_180;
+ break;
+ case 270:
+ orientation = ExifInterface.ORIENTATION_ROTATE_270;
+ break;
}
- if (DEBUG) {
- Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
- }
+ mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
+ ExifAttribute.createUShort(orientation, mExifByteOrder));
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
}
} finally {
retriever.release();
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index ed5f7d8..c475e12 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -96,6 +96,19 @@
* <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
* <tr><td>{@link #KEY_LANGUAGE}</td><td>String</td><td>The language of the content.</td></tr>
* </table>
+ *
+ * Image formats have the following keys:
+ * <table>
+ * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
+ * <tr><td>{@link #KEY_WIDTH}</td><td>Integer</td><td></td></tr>
+ * <tr><td>{@link #KEY_HEIGHT}</td><td>Integer</td><td></td></tr>
+ * <tr><td>{@link #KEY_COLOR_FORMAT}</td><td>Integer</td><td>set by the user
+ * for encoders, readable in the output format of decoders</b></td></tr>
+ * <tr><td>{@link #KEY_GRID_WIDTH}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_HEIGHT}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_ROWS}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_COLS}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * </table>
*/
public final class MediaFormat {
public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
@@ -126,6 +139,35 @@
public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
/**
+ * MIME type for HEIF still image data encoded in HEVC.
+ *
+ * To decode such an image, {@link MediaCodec} decoder for
+ * {@ #MIMETYPE_VIDEO_HEVC} shall be used. The client needs to form
+ * the correct {@link #MediaFormat} based on additional information in
+ * the track format, and send it to {@link MediaCodec#configure}.
+ *
+ * The track's MediaFormat will come with {@link #KEY_WIDTH} and
+ * {@link #KEY_HEIGHT} keys, which describes the width and height
+ * of the image. If the image doesn't contain grid (i.e. none of
+ * {@link #KEY_GRID_WIDTH}, {@link #KEY_GRID_HEIGHT},
+ * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLS} are present}), the
+ * track will contain a single sample of coded data for the entire image,
+ * and the image width and height should be used to set up the decoder.
+ *
+ * If the image does come with grid, each sample from the track will
+ * contain one tile in the grid, of which the size is described by
+ * {@link #KEY_GRID_WIDTH} and {@link #KEY_GRID_HEIGHT}. This size
+ * (instead of {@link #KEY_WIDTH} and {@link #KEY_HEIGHT}) should be
+ * used to set up the decoder. The track contains {@link #KEY_GRID_ROWS}
+ * by {@link #KEY_GRID_COLS} samples in row-major, top-row first,
+ * left-to-right order. The output image should be reconstructed by
+ * first tiling the decoding results of the tiles in the correct order,
+ * then trimming (before rotation is applied) on the bottom and right
+ * side, if the tiled area is larger than the image width and height.
+ */
+ public static final String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
+
+ /**
* MIME type for WebVTT subtitle data.
*/
public static final String MIMETYPE_TEXT_VTT = "text/vtt";
@@ -232,6 +274,54 @@
public static final String KEY_FRAME_RATE = "frame-rate";
/**
+ * A key describing the grid width of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC}
+ * track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_HEIGHT
+ * @see #KEY_GRID_ROWS
+ * @see #KEY_GRID_COLS
+ */
+ public static final String KEY_GRID_WIDTH = "grid-width";
+
+ /**
+ * A key describing the grid height of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC}
+ * track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_WIDTH
+ * @see #KEY_GRID_ROWS
+ * @see #KEY_GRID_COLS
+ */
+ public static final String KEY_GRID_HEIGHT = "grid-height";
+
+ /**
+ * A key describing the number of grid rows in the content in a
+ * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_WIDTH
+ * @see #KEY_GRID_HEIGHT
+ * @see #KEY_GRID_COLS
+ */
+ public static final String KEY_GRID_ROWS = "grid-rows";
+
+ /**
+ * A key describing the number of grid columns in the content in a
+ * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_WIDTH
+ * @see #KEY_GRID_HEIGHT
+ * @see #KEY_GRID_ROWS
+ */
+ public static final String KEY_GRID_COLS = "grid-cols";
+
+ /**
* A key describing the raw audio sample encoding/format.
*
* <p>The associated value is an integer, using one of the
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 760cc49..0b86401 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -47,7 +47,7 @@
// The field below is accessed by native methods
@SuppressWarnings("unused")
private long mNativeContext;
-
+
private static final int EMBEDDED_PICTURE_TYPE_ANY = 0xFFFF;
public MediaMetadataRetriever() {
@@ -58,7 +58,7 @@
* Sets the data source (file pathname) to use. Call this
* method before the rest of the methods in this class. This method may be
* time-consuming.
- *
+ *
* @param path The path of the input media file.
* @throws IllegalArgumentException If the path is invalid.
*/
@@ -113,7 +113,7 @@
* responsibility to close the file descriptor. It is safe to do so as soon
* as this call returns. Call this method before the rest of the methods in
* this class. This method may be time-consuming.
- *
+ *
* @param fd the FileDescriptor for the file you want to play
* @param offset the offset into the file where the data to be played starts,
* in bytes. It must be non-negative
@@ -123,13 +123,13 @@
*/
public native void setDataSource(FileDescriptor fd, long offset, long length)
throws IllegalArgumentException;
-
+
/**
* Sets the data source (FileDescriptor) to use. It is the caller's
* responsibility to close the file descriptor. It is safe to do so as soon
* as this call returns. Call this method before the rest of the methods in
* this class. This method may be time-consuming.
- *
+ *
* @param fd the FileDescriptor for the file you want to play
* @throws IllegalArgumentException if the FileDescriptor is invalid
*/
@@ -138,11 +138,11 @@
// intentionally less than LONG_MAX
setDataSource(fd, 0, 0x7ffffffffffffffL);
}
-
+
/**
- * Sets the data source as a content Uri. Call this method before
+ * Sets the data source as a content Uri. Call this method before
* the rest of the methods in this class. This method may be time-consuming.
- *
+ *
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @throws IllegalArgumentException if the Uri is invalid
@@ -154,7 +154,7 @@
if (uri == null) {
throw new IllegalArgumentException();
}
-
+
String scheme = uri.getScheme();
if(scheme == null || scheme.equals("file")) {
setDataSource(uri.getPath());
@@ -213,12 +213,12 @@
/**
* Call this method after setDataSource(). This method retrieves the
* meta data value associated with the keyCode.
- *
+ *
* The keyCode currently supported is listed below as METADATA_XXX
* constants. With any other value, it returns a null pointer.
- *
+ *
* @param keyCode One of the constants listed below at the end of the class.
- * @return The meta data value associate with the given keyCode on success;
+ * @return The meta data value associate with the given keyCode on success;
* null on failure.
*/
public native String extractMetadata(int keyCode);
@@ -357,6 +357,109 @@
private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height);
/**
+ * This method retrieves a video frame by its index. It should only be called
+ * after {@link #setDataSource}.
+ *
+ * @param frameIndex 0-based index of the video frame. The frame index must be that of
+ * a valid frame. The total number of frames available for retrieval can be queried
+ * via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
+ *
+ * @throws IllegalStateException if the container doesn't contain video or image sequences.
+ * @throws IllegalArgumentException if the requested frame index does not exist.
+ *
+ * @return A Bitmap containing the requested video frame, or null if the retrieval fails.
+ *
+ * @see #getFramesAtIndex(int, int)
+ */
+ public Bitmap getFrameAtIndex(int frameIndex) {
+ Bitmap[] bitmaps = getFramesAtIndex(frameIndex, 1);
+ if (bitmaps == null || bitmaps.length < 1) {
+ return null;
+ }
+ return bitmaps[0];
+ }
+
+ /**
+ * This method retrieves a consecutive set of video frames starting at the
+ * specified index. It should only be called after {@link #setDataSource}.
+ *
+ * If the caller intends to retrieve more than one consecutive video frames,
+ * this method is preferred over {@link #getFrameAtIndex(int)} for efficiency.
+ *
+ * @param frameIndex 0-based index of the first video frame to retrieve. The frame index
+ * must be that of a valid frame. The total number of frames available for retrieval
+ * can be queried via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
+ * @param numFrames number of consecutive video frames to retrieve. Must be a positive
+ * value. The stream must contain at least numFrames frames starting at frameIndex.
+ *
+ * @throws IllegalStateException if the container doesn't contain video or image sequences.
+ * @throws IllegalArgumentException if the frameIndex or numFrames is invalid, or the
+ * stream doesn't contain at least numFrames starting at frameIndex.
+
+ * @return An array of Bitmaps containing the requested video frames. The returned
+ * array could contain less frames than requested if the retrieval fails.
+ *
+ * @see #getFrameAtIndex(int)
+ */
+ public Bitmap[] getFramesAtIndex(int frameIndex, int numFrames) {
+ if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO))) {
+ throw new IllegalStateException("Does not contail video or image sequences");
+ }
+ int frameCount = Integer.parseInt(
+ extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
+ if (frameIndex < 0 || numFrames < 1
+ || frameIndex >= frameCount
+ || frameIndex > frameCount - numFrames) {
+ throw new IllegalArgumentException("Invalid frameIndex or numFrames: "
+ + frameIndex + ", " + numFrames);
+ }
+ return _getFrameAtIndex(frameIndex, numFrames);
+ }
+ private native Bitmap[] _getFrameAtIndex(int frameIndex, int numFrames);
+
+ /**
+ * This method retrieves a still image by its index. It should only be called
+ * after {@link #setDataSource}.
+ *
+ * @param imageIndex 0-based index of the image, with negative value indicating
+ * the primary image.
+ * @throws IllegalStateException if the container doesn't contain still images.
+ * @throws IllegalArgumentException if the requested image does not exist.
+ *
+ * @return the requested still image, or null if the image cannot be retrieved.
+ *
+ * @see #getPrimaryImage
+ */
+ public Bitmap getImageAtIndex(int imageIndex) {
+ if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE))) {
+ throw new IllegalStateException("Does not contail still images");
+ }
+
+ String imageCount = extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT);
+ if (imageIndex >= Integer.parseInt(imageCount)) {
+ throw new IllegalArgumentException("Invalid image index: " + imageCount);
+ }
+
+ return _getImageAtIndex(imageIndex);
+ }
+
+ /**
+ * This method retrieves the primary image of the media content. It should only
+ * be called after {@link #setDataSource}.
+ *
+ * @return the primary image, or null if it cannot be retrieved.
+ *
+ * @throws IllegalStateException if the container doesn't contain still images.
+ *
+ * @see #getImageAtIndex(int)
+ */
+ public Bitmap getPrimaryImage() {
+ return getImageAtIndex(-1);
+ }
+
+ private native Bitmap _getImageAtIndex(int imageIndex);
+
+ /**
* Call this method after setDataSource(). This method finds the optional
* graphic or album/cover art associated associated with the data source. If
* there are more than one pictures, (any) one of them is returned.
@@ -572,5 +675,40 @@
* number.
*/
public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25;
+ /**
+ * If this key exists the media contains still image content.
+ */
+ public static final int METADATA_KEY_HAS_IMAGE = 26;
+ /**
+ * If the media contains still images, this key retrieves the number
+ * of still images.
+ */
+ public static final int METADATA_KEY_IMAGE_COUNT = 27;
+ /**
+ * If the media contains still images, this key retrieves the image
+ * index of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_PRIMARY = 28;
+ /**
+ * If the media contains still images, this key retrieves the width
+ * of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_WIDTH = 29;
+ /**
+ * If the media contains still images, this key retrieves the height
+ * of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_HEIGHT = 30;
+ /**
+ * If the media contains still images, this key retrieves the rotation
+ * of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_ROTATION = 31;
+ /**
+ * If the media contains video and this key exists, it retrieves the
+ * total number of frames in the video sequence.
+ */
+ public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32;
+
// Add more here...
}
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 4659ae1..42ebf6a 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -244,29 +244,9 @@
}
}
-static jobject android_media_MediaMetadataRetriever_getFrameAtTime(
- JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height)
-{
- ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",
- (long long)timeUs, option, dst_width, dst_height);
- MediaMetadataRetriever* retriever = getRetriever(env, thiz);
- if (retriever == 0) {
- jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
- return NULL;
- }
-
- // Call native method to retrieve a video frame
- VideoFrame *videoFrame = NULL;
- sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
- if (frameMemory != 0) { // cast the shared structure to a VideoFrame object
- videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
- }
- if (videoFrame == NULL) {
- ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
- return NULL;
- }
-
- ALOGV("Dimension = %dx%d and bytes = %d",
+static jobject getBitmapFromVideoFrame(
+ JNIEnv *env, VideoFrame *videoFrame, jint dst_width, jint dst_height) {
+ ALOGV("getBitmapFromVideoFrame: dimension = %dx%d and bytes = %d",
videoFrame->mDisplayWidth,
videoFrame->mDisplayHeight,
videoFrame->mSize);
@@ -301,7 +281,7 @@
if (env->ExceptionCheck()) {
env->ExceptionClear();
}
- ALOGE("getFrameAtTime: create Bitmap failed!");
+ ALOGE("getBitmapFromVideoFrame: create Bitmap failed!");
return NULL;
}
@@ -340,6 +320,93 @@
return jBitmap;
}
+static jobject android_media_MediaMetadataRetriever_getFrameAtTime(
+ JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height)
+{
+ ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",
+ (long long)timeUs, option, dst_width, dst_height);
+ MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+ if (retriever == 0) {
+ jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
+ return NULL;
+ }
+
+ // Call native method to retrieve a video frame
+ VideoFrame *videoFrame = NULL;
+ sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
+ if (frameMemory != 0) { // cast the shared structure to a VideoFrame object
+ videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+ }
+ if (videoFrame == NULL) {
+ ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
+ return NULL;
+ }
+
+ return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height);
+}
+
+static jobject android_media_MediaMetadataRetriever_getImageAtIndex(
+ JNIEnv *env, jobject thiz, jint index)
+{
+ ALOGV("getImageAtIndex: index %d", index);
+ MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+ if (retriever == 0) {
+ jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
+ return NULL;
+ }
+
+ // Call native method to retrieve an image
+ VideoFrame *videoFrame = NULL;
+ sp<IMemory> frameMemory = retriever->getImageAtIndex(index);
+ if (frameMemory != 0) { // cast the shared structure to a VideoFrame object
+ videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+ }
+ if (videoFrame == NULL) {
+ ALOGE("getImageAtIndex: videoFrame is a NULL pointer");
+ return NULL;
+ }
+
+ return getBitmapFromVideoFrame(env, videoFrame, -1, -1);
+}
+
+static jobjectArray android_media_MediaMetadataRetriever_getFrameAtIndex(
+ JNIEnv *env, jobject thiz, jint frameIndex, jint numFrames)
+{
+ ALOGV("getFrameAtIndex: frameIndex %d, numFrames %d", frameIndex, numFrames);
+ MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+ if (retriever == 0) {
+ jniThrowException(env,
+ "java/lang/IllegalStateException", "No retriever available");
+ return NULL;
+ }
+
+ std::vector<sp<IMemory> > frames;
+ status_t err = retriever->getFrameAtIndex(&frames, frameIndex, numFrames);
+ if (err != OK || frames.size() == 0) {
+ ALOGE("failed to get frames from retriever, err=%d, size=%zu",
+ err, frames.size());
+ return NULL;
+ }
+
+ jobjectArray bitmapArrayObj = env->NewObjectArray(
+ frames.size(), fields.bitmapClazz, NULL);
+ if (bitmapArrayObj == NULL) {
+ ALOGE("can't create bitmap array object");
+ return NULL;
+ }
+
+ for (size_t i = 0; i < frames.size(); i++) {
+ if (frames[i] == NULL || frames[i]->pointer() == NULL) {
+ ALOGE("video frame at index %zu is a NULL pointer", frameIndex + i);
+ continue;
+ }
+ VideoFrame *videoFrame = static_cast<VideoFrame *>(frames[i]->pointer());
+ jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1);
+ env->SetObjectArrayElement(bitmapArrayObj, i, bitmapObj);
+ }
+ return bitmapArrayObj;
+}
+
static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
JNIEnv *env, jobject thiz, jint pictureType)
{
@@ -485,6 +552,8 @@
{"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
{"_setDataSource", "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaMetadataRetriever_setDataSourceCallback},
{"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
+ {"_getImageAtIndex", "(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getImageAtIndex},
+ {"_getFrameAtIndex", "(II)[Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtIndex},
{"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
{"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
{"release", "()V", (void *)android_media_MediaMetadataRetriever_release},
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 0da6289..fe2a939 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -955,6 +955,7 @@
outThumbSize = image_data.thumbnail.length;
} else {
free(result);
+ result = NULL;
}
}
break;
diff --git a/packages/BackupRestoreConfirmation/res/values-hy/strings.xml b/packages/BackupRestoreConfirmation/res/values-hy/strings.xml
index 285c15d..ca9834e 100644
--- a/packages/BackupRestoreConfirmation/res/values-hy/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-hy/strings.xml
@@ -32,7 +32,7 @@
<string name="backup_enc_password_required" msgid="7889652203371654149">"Քանի որ ձեր սարքը գաղտնագրված է, դուք պետք է գաղտնագրեք նաև ձեր պահուստը: Խնդրում ենք ստորև սահմանել գաղտնաբառը՝"</string>
<string name="restore_enc_password_text" msgid="6140898525580710823">"Եթե վերականգնվող տվյալները գաղտնագրված են, խնդրում ենք մուտքագրել գաղտնաբառը ստորև`"</string>
<string name="toast_backup_started" msgid="550354281452756121">"Պահուստավորումը սկսվում է..."</string>
- <string name="toast_backup_ended" msgid="3818080769548726424">"Պահուստավորումն ավարտվեց"</string>
+ <string name="toast_backup_ended" msgid="3818080769548726424">"Պահուստավորումն ավարտված է"</string>
<string name="toast_restore_started" msgid="7881679218971277385">"Վերականգնումը մեկնարկեց..."</string>
<string name="toast_restore_ended" msgid="1764041639199696132">"Վերականգնումն ավարտվեց"</string>
<string name="toast_timeout" msgid="5276598587087626877">"Գործողության ժամանակը սպառվեց"</string>
diff --git a/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java b/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java
index bbbdf80..8a06cc6 100644
--- a/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java
+++ b/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java
@@ -26,6 +26,7 @@
public class Ocquarium extends Activity {
ImageView mImageView;
+ private OctopusDrawable mOcto;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -43,10 +44,9 @@
bg.addView(mImageView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
- final OctopusDrawable octo = new OctopusDrawable(getApplicationContext());
- octo.setSizePx((int) (OctopusDrawable.randfrange(40f,180f) * dp));
- mImageView.setImageDrawable(octo);
- octo.startDrift();
+ mOcto = new OctopusDrawable(getApplicationContext());
+ mOcto.setSizePx((int) (OctopusDrawable.randfrange(40f,180f) * dp));
+ mImageView.setImageDrawable(mOcto);
mImageView.setOnTouchListener(new View.OnTouchListener() {
boolean touching;
@@ -54,24 +54,36 @@
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- if (octo.hitTest(motionEvent.getX(), motionEvent.getY())) {
+ if (mOcto.hitTest(motionEvent.getX(), motionEvent.getY())) {
touching = true;
- octo.stopDrift();
+ mOcto.stopDrift();
}
break;
case MotionEvent.ACTION_MOVE:
if (touching) {
- octo.moveTo(motionEvent.getX(), motionEvent.getY());
+ mOcto.moveTo(motionEvent.getX(), motionEvent.getY());
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
touching = false;
- octo.startDrift();
+ mOcto.startDrift();
break;
}
return true;
}
});
}
+
+ @Override
+ protected void onPause() {
+ mOcto.stopDrift();
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mOcto.startDrift();
+ }
}
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
new file mode 100644
index 0000000..4211c27
--- /dev/null
+++ b/packages/SettingsLib/OWNERS
@@ -0,0 +1,21 @@
+# People who can approve changes for submission
+asapperstein@google.com
+asargent@google.com
+dehboxturtle@google.com
+dhnishi@google.com
+dling@google.com
+dsandler@google.com
+evanlaird@google.com
+jackqdyulei@google.com
+jmonk@google.com
+mfritze@google.com
+nicoya@google.com
+rogerxue@google.com
+virgild@google.com
+zhfan@google.com
+
+# Emergency approvers in case the above are not available
+miket@google.com
+
+# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
+per-file *.xml=*
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_bt_laptop.xml b/packages/SettingsLib/res/drawable/ic_bt_laptop.xml
new file mode 100644
index 0000000..029e4d9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_laptop.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,18c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0
+ -2,0.9 -2,2v10c0,1.1 0.9,2 2,2H0v2h24v-2h-4zM4,6h16v10H4V6z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_settings_bluetooth.xml b/packages/SettingsLib/res/drawable/ic_settings_bluetooth.xml
new file mode 100644
index 0000000..6e32e1a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_settings_bluetooth.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M13.5,12l3.8,-3.7c0.4,-0.4 0.4,-1.1 0,-1.5l-4.5,-4.5c-0.4,-0.4 -1.1,-0.4 -1.5,0.1C11.1,2.5 11,2.8 11,3v6.4L6.9,5.4C6.5,5 5.9,5 5.5,5.4s-0.4,1.1 0,1.5l5.1,5.1l-5.1,5.1c-0.4,0.4 -0.4,1.1 0,1.5s1.1,0.4 1.5,0l4.1,-4V21c0,0.6 0.5,1 1,1c0.3,0 0.5,-0.1 0.7,-0.3l0.1,0l4.5,-4.5c0.4,-0.4 0.4,-1.1 0,-1.5L13.5,12zM13,9.7V5.4l2.1,2.2L13,9.7zM13,18.6v-4.3l2.1,2.2L13,18.6z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_settings_print.xml b/packages/SettingsLib/res/drawable/ic_settings_print.xml
new file mode 100644
index 0000000..0eab402
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_settings_print.xml
@@ -0,0 +1,28 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M19,8H5c-1.66,0-3,1.34-3,3v5c0,0.55,0.45,1,1,1h3v3c0,0.55,0.45,1,1,1h10c0.55,0,1-0.45,1-1v-3h3c0.55,0,1-0.45,1-1v-5
+C22,9.34,20.66,8,19,8z M16,19H8v-5h8V19z
+M19,12c-0.55,0-1-0.45-1-1s0.45-1,1-1s1,0.45,1,1S19.55,12,19,12z M17,3H7
+C6.45,3,6,3.45,6,4v3h12V4C18,3.45,17.55,3,17,3z"/>
+</vector>
diff --git a/packages/SettingsLib/res/layout/preference_two_target.xml b/packages/SettingsLib/res/layout/preference_two_target.xml
index c2167f3..4658924 100644
--- a/packages/SettingsLib/res/layout/preference_two_target.xml
+++ b/packages/SettingsLib/res/layout/preference_two_target.xml
@@ -33,19 +33,17 @@
android:background="?android:attr/selectableItemBackground"
android:gravity="start|center_vertical"
android:clipToPadding="false"
- android:layout_marginStart="4dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<LinearLayout
android:id="@+id/icon_frame"
- style="@style/preference_icon_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start|center_vertical"
+ android:minWidth="56dp"
android:orientation="horizontal"
android:clipToPadding="false"
- android:paddingEnd="12dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<android.support.v7.internal.widget.PreferenceImageView
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index a7a7ea9..145060b 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth-oudio-LDAC-kodek: Speelgehalte"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Kies Bluetooth-oudio-LDAC-kodek:\nSpeelgehalte"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Stroming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS oor TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Indien dit geaktiveer is, probeer DNS oor TLS op poort 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Wys opsies vir draadlose skermsertifisering"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Verhoog Wi-Fi-aantekeningvlak, wys per SSID RSSI in Wi‑Fi-kieser"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Wanneer dit geaktiveer is, sal Wi-Fi die dataverbinding aggressiewer na mobiel oordra wanneer die Wi-Fi-sein swak is"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index bff8fe2..0abf21b 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"የብሉቱዝ ኦዲዮ LDAC ኮዴክ ይምረጡ፦ የመልሶ ማጫወት ጥራት"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"የብሉቱዝ ኦዲዮ LDAC ኮዴክ ይምረጡ፦\nየመልሶ ማጫወት ጥራት"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ዥረት፦ <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS በTLS ላይ"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"ከነቃ በወደብ 853 ላይ DNS በTLS ላይ ይሞክሩት።"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"የገመድ አልባ ማሳያ እውቅና ማረጋገጫ አማራጮችን አሳይ"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"የWi‑Fi ምዝግብ ማስታወሻ አያያዝ ደረጃ ጨምር፣ በWi‑Fi መምረጫ ውስጥ በአንድ SSID RSSI አሳይ"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ሲነቃ የWi‑Fi ምልክት ዝቅተኛ ሲሆን Wi‑Fi የውሂብ ግንኙነት ለሞባይል ማስረከብ ላይ ይበልጥ አስገዳጅ ይሆናል"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 457a6dd..2dc8dc9 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"برنامج ترميز LDAC لصوت البلوتوث: جودة التشغيل"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"اختيار برنامج ترميز LDAC لصوت البلوتوث:\nجودة التشغيل"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"البث: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"نظام أسماء النطاقات عبر طبقة النقل الآمنة"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"عند تمكينه، يمكن استخدام نظام أسماء النطاقات عبر طبقة النقل الآمنة على المنفذ 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"عرض خيارات شهادة عرض شاشة لاسلكي"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"زيادة مستوى تسجيل Wi-Fi، وعرض لكل SSID RSSI في منتقي Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"عند تمكينه، سيكون Wi-Fi أكثر حدة في تسليم اتصال البيانات إلى الجوّال، وذلك عندما تكون إشارة WiFi منخفضة"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 8b453ab..0ae9025 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Kodeki:Oxutma Keyfiyyəti"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth Audio LDAC Kodek:\nOxutma Keyfiyyəti Seçin"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Canlı yayım: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS ilə DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Aktiv edilərsə, 853 nömrəli portda TLS ilə DNS-i sınayın."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Simsiz displey sertifikatlaşması üçün seçimləri göstərir"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi giriş səviyyəsini qaldırın, Wi‑Fi seçəndə hər SSID RSSI üzrə göstərin"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Aktiv edildikdə, Wi-Fi siqnalı zəif olan zaman, data bağlantısını mobilə ötürərəkən Wi-Fi daha aqressiv olacaq"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index ba0de57..ef4e48e 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth audio kodek LDAC: kvalitet reprodukcije"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Izaberite Bluetooth audio kodek LDAC:\nkvalitet reprodukcije"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Strimovanje: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS preko TLS-a"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Ako je omogućeno, probajte DNS preko TLS-a na portu 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Prikaz opcija za sertifikaciju bežičnog ekrana"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povećava nivo evidentiranja za Wi‑Fi. Prikaz po SSID RSSI-u u biraču Wi‑Fi mreže"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kad se omogući, Wi‑Fi će biti agresivniji pri prebacivanju mreže za prenos podataka na mobilnu ako je Wi‑Fi signal slab"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 49a18b6..e855f9b 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Аўдыякодэк Bluetooth LDAC: якасць прайгравання"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Выбраць аўдыякодэк Bluetooth LDAC:\nякасць прайгравання"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Перадача плынню: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS праз TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Калі магчыма, паспрабаваць DNS праз TLS на порце 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Паказаць опцыі сертыфікацыі бесправаднога дысплея"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Падвыс. узровень дэтал-цыі журнала Wi‑Fi у залежн. ад SSID RSSI у Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Калі гэта функцыя ўключана, Wi-Fi будзе больш інтэнсіўна імкнуцца перайсці на падключ. маб. перад. даных пры слабым сігнале Wi‑Fi"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 8fbef19..d8b650a 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Кодек за звука през Bluetooth с технологията LDAC: Качество на възпроизвеждане"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Изберете кодек за звука през Bluetooth с технологията LDAC:\nКачество на възпроизвеждане"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Поточно предаване: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS през TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Ако опцията е активирана, изпробвайте DNS през TLS на порт 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Показване на опциите за сертифициране на безжичния дисплей"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"По-подробно регистр. на Wi‑Fi – данни за RSSI на SSID в инстр. за избор на Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"При активиране предаването на връзката за данни от Wi-Fi към мобилната мрежа ще е по-агресивно, когато сигналът за Wi-Fi е слаб"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index ec48835..24a9084 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ব্লুটুথ অডিও LDAC কোডেক: প্লেব্যাক গুণমান"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ব্লুটুথ অডিও LDAC কোডেক বেছে নিন:\nপ্লেব্যাক গুণমান"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"স্ট্রিমিং: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS এ ডিএনএস"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"যদি সক্ষম করা থাকে তাহলে পোর্ট ৮৫৩ তে TLS এ ডিএনএস এর চেষ্টা করুন।"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ওয়্যারলেস প্রদর্শন সার্টিফিকেশন জন্য বিকল্পগুলি দেখান"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"ওয়াই-ফাই লগিং স্তর বাড়ান, ওয়াই-ফাই চয়নকারীতে SSID RSSI অনুযায়ী দেখান"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"সক্ষম করা থাকলে, ওয়াই ফাই সিগন্যালের মান খারাপ হলে ডেটা সংযোগ মোবাইলের কাছে হস্তান্তর করার জন্য ওয়াই ফাই আরো বেশি তৎপর হবে।"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index a331286..ca610f7 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC kodek: Kvalitet reprodukcije"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Odaberite Bluetooth Audio LDAC kodek:\nKvalitet reprodukcije"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Prijenos: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS preko TLS-a"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Ako je opcija omogućena, pokušaj DNS preko TLS-a na priključku 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Pokaži opcije za certifikaciju Bežičnog prikaza"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povećajte nivo Wi-Fi zapisivanja, pokazati po SSID RSSI Wi-Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kada je omogućeno, Wi-Fi veza će u slučaju slabog signala agresivnije predavati vezu za prijenos podataka na mobilnu vezu"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 145f61c..0ee111a 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Còdec LDAC d\'àudio per Bluetooth: qualitat de reproducció"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecciona el còdec LDAC d\'àudio per Bluetooth:\nQualitat de reproducció"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"S\'està reproduint en temps real: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS per TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Si s\'ha activat, prova d\'utilitzar DNS per TLS al port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostra les opcions de certificació de pantalla sense fil"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Augmenta nivell de registre Wi‑Fi i mostra\'l per SSID RSSI al Selector de Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Quan s\'activa, la Wi-Fi és més agressiva en transferir la connexió de dades al mòbil quan el senyal de la Wi-Fi sigui dèbil"</string>
@@ -234,8 +236,8 @@
<string name="verify_apps_over_usb_title" msgid="4177086489869041953">"Verifica aplicacions per USB"</string>
<string name="verify_apps_over_usb_summary" msgid="9164096969924529200">"Comprova les aplicacions instal·lades mitjançant ADB/ADT per detectar possibles comportaments perillosos"</string>
<string name="bluetooth_show_devices_without_names_summary" msgid="2351196058115755520">"Es mostraran els dispositius Bluetooth sense el nom (només l\'adreça MAC)"</string>
- <string name="bluetooth_disable_absolute_volume_summary" msgid="6031284410786545957">"Desactiva la funció de volum absolut de Bluetooth en cas que es produeixin problemes de volum amb dispositius remots, com ara un volum massa alt o una manca de control."</string>
- <string name="bluetooth_enable_inband_ringing_summary" msgid="2787866074741784975">"Permet que els sons de trucada del telèfon es reprodueixin en auriculars amb Bluetooth"</string>
+ <string name="bluetooth_disable_absolute_volume_summary" msgid="6031284410786545957">"Desactiva la funció de volum absolut del Bluetooth en cas que es produeixin problemes de volum amb dispositius remots, com ara un volum massa alt o una manca de control."</string>
+ <string name="bluetooth_enable_inband_ringing_summary" msgid="2787866074741784975">"Permet que els sons de trucada del telèfon es reprodueixin en auriculars Bluetooth"</string>
<string name="enable_terminal_title" msgid="95572094356054120">"Terminal local"</string>
<string name="enable_terminal_summary" msgid="67667852659359206">"Activa l\'aplicació de terminal que ofereix accés al shell local"</string>
<string name="hdcp_checking_title" msgid="8605478913544273282">"Comprovació HDCP"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 994afae..60bbbf3 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodek Bluetooth Audio LDAC: Kvalita přehrávání"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Vyberte kodek Bluetooth Audio LDAC:\nKvalita přehrávání"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streamování: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS přes TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Pokud tuto možnost aktivujete, zařízení se bude připojovat k serverům DNS pomocí protokolu TLS na portu 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Zobrazit možnosti certifikace bezdrátového displeje"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Zvýšit úroveň protokolování Wi‑Fi zobrazenou v SSID a RSSI při výběru sítě Wi‑Fi."</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Pokud je tato možnost zapnuta, bude síť Wi-Fi při předávání datového připojení mobilní síti při slabém signálu Wi-Fi agresivnější."</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index e916853..089c43c 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"LDAC-codec for Bluetooth-lyd: Afspilningskvalitet"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Vælg LDAC-codec for Bluetooth-lyd:\nAfspilningskvalitet"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streamer: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS via TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Prøv DNS via TLS (hvis muligheden er aktiveret) på port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Vis valgmuligheder for certificering af trådløs skærm"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Øg mængden af Wi‑Fi-logføring. Vis opdelt efter SSID RSSI i Wi‑Fi-vælgeren"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Når dette er aktiveret, gennemtvinges en overdragelse af dataforbindelsen fra Wi-Fi til mobilnetværk, når Wi-Fi-signalet er svagt"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 1ec4fee..2635e66 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth-Audio-LDAC-Codec: Wiedergabequalität"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth-Audio-LDAC-Codec auswählen:\nWiedergabequalität"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS-over-TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Wenn diese Option aktiviert ist, versuche für Port 853 DNS-over-TLS anzuwenden."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Optionen zur Zertifizierung für kabellose Übertragung anzeigen"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Level für WLAN-Protokollierung erhöhen, in WiFi Picker pro SSID-RSSI anzeigen"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Wenn diese Option aktiviert ist, ist das WLAN bei schwachem Signal bei der Übergabe der Datenverbindung an den Mobilfunk aggressiver"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 2548a5d..b6b1bde 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Κωδικοποιητής LDAC ήχου Bluetooth: Ποιότητα αναπαραγωγής"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Επιλογή κωδικοποιητή LDAC ήχου Bluetooth:\nΠοιότητα αναπαραγωγής"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Ροή: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS μέσω TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Εάν ενεργοποιηθεί, γίνεται προσπάθεια DNS μέσω TLS στη θύρα 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Εμφάνιση επιλογών για πιστοποίηση ασύρματης οθόνης"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Αύξηση επιπέδου καταγ. Wi-Fi, εμφάνιση ανά SSID RSSI στο εργαλείο επιλογής Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Όταν είναι ενεργό, το Wi-Fi θα μεταβιβάζει πιο επιθετικά τη σύνδ.δεδομένων σε δίκτυο κινητής τηλ., όταν το σήμα Wi-Fi είναι χαμηλό"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 9e24004..e85242a 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"If enabled, attempt DNS over TLS on port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to mobile, when Wi‑Fi signal is low"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 9e24004..e85242a 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"If enabled, attempt DNS over TLS on port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to mobile, when Wi‑Fi signal is low"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 9e24004..e85242a 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"If enabled, attempt DNS over TLS on port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to mobile, when Wi‑Fi signal is low"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 9e24004..e85242a 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"If enabled, attempt DNS over TLS on port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to mobile, when Wi‑Fi signal is low"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 77c0e4f..a3430fc 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"If enabled, attempt DNS over TLS on port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to mobile, when Wi‑Fi signal is low"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index ff26eef..ea62383 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -163,7 +163,7 @@
<string name="choose_profile" msgid="6921016979430278661">"Elegir perfil"</string>
<string name="category_personal" msgid="1299663247844969448">"Personal"</string>
<string name="category_work" msgid="8699184680584175622">"Trabajo"</string>
- <string name="development_settings_title" msgid="215179176067683667">"Opciones del programador"</string>
+ <string name="development_settings_title" msgid="215179176067683667">"Opciones para programadores"</string>
<string name="development_settings_enable" msgid="542530994778109538">"Activar opciones para programador"</string>
<string name="development_settings_summary" msgid="1815795401632854041">"Establecer opciones para desarrollar aplicaciones"</string>
<string name="development_settings_not_available" msgid="4308569041701535607">"Las opciones de programador no están disponibles para este usuario."</string>
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Códec del audio Bluetooth LDAC: calidad de reproducción"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Seleccionar códec del audio Bluetooth LDAC:\nCalidad de reproducción"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmitiendo: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS mediante TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Si esta opción está habilitada, prueba DNS mediante TLS en el puerto 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opciones de certificación de pantalla inalámbrica"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar nivel de registro Wi-Fi; mostrar por SSID RSSI en el selector de Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Si habilitas esta opción, se priorizará el cambio de Wi-Fi a datos móviles cuando la señal de Wi-Fi sea débil"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 20bab13..8474c49 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Selecciona el códec LDAC por Bluetooth: calidad de reproducción"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecciona el códec LDAC de audio por Bluetooth:\nCalidad de reproducción"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS a través de TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Si esta opción está inhabilitada, prueba DNS a través de TLS en el puerto 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opciones para la certificación de la pantalla inalámbrica"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar el nivel de registro de Wi-Fi, mostrar por SSID RSSI en el selector Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Si se activa esta opción, la conexión Wi-Fi será más agresiva al pasar la conexión a datos móviles (si la señal Wi-Fi es débil)"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index a0341fb..efbbac1 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetoothi LDAC-helikodek: taasesituskvaliteet"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Valige Bluetoothi LDAC-helikodek:\ntaasesituskvaliteet"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Voogesitus: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS TLS-i kaudu"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Kui on lubatud, katsetatakse DNS-i TLS-i kaudu (port 853)."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Juhtmeta ekraaniühenduse sertifitseerimisvalikute kuvamine"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Suurenda WiFi logimistaset, kuva WiFi valijas SSID RSSI järgi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kui seade on lubatud, asendatakse nõrga signaaliga WiFi-ühendus agressiivsemalt mobiilse andmesideühendusega"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index e8d9a5e..bd2f94e 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth audioaren LDAC kodeka: erreprodukzioaren kalitatea"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Hautatu Bluetooth audioaren LDAC kodeka:\nerreprodukzioaren kalitatea"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Igortzean: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS bidezko DNSa"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Gaituz gero, erabili TLS bidezko DNSa 853 atakan."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Erakutsi hari gabeko bistaratze-egiaztapenaren aukerak"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Erakutsi datu gehiago Wi-Fi sareetan saioa hasterakoan. Erakutsi sarearen identifikatzailea eta seinalearen indarra Wi‑Fi sareen hautagailuan."</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Aukera hori gaituz gero, gailua nahitaez aldatuko da datu mugikorren konexiora Wi-Fi seinalea ahultzen dela nabaritutakoan"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 326192d..bc287bc 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"کدک LDAC صوتی بلوتوث: کیفیت پخش"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"انتخاب کدک LDAC صوتی بلوتوث:\nکیفیت پخش"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"پخش جریانی: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS ازطریق امنیت لایه انتقال"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"درصورت فعال بودن، DNS ازطریق امنیت لایه انتقال در درگاه ۸۵۳ انجام شود."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"نمایش گزینهها برای گواهینامه نمایش بیسیم"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"افزایش سطح گزارشگیری Wi‑Fi، نمایش به ازای SSID RSSI در انتخابکننده Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"زمانیکه فعال است، درشرایطی که سیگنال Wi-Fi ضعیف باشد، Wi‑Fi برای واگذاری اتصال داده به دستگاه همراه قویتر عمل خواهد کرد."</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 6321086..6742aca 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth-äänen LDAC-koodekki: Toiston laatu"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Valitse Bluetooth-äänen LDAC-koodekki:\nToiston laatu"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Striimaus: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS ennen TLS:ää"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Jos tämä on käytössä, yritä käyttää DNS:ää mieluummin kuin TLS:ää portin 853 kautta."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Näytä langattoman näytön sertifiointiin liittyvät asetukset"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Lisää Wi‑Fin lokikirjaustasoa, näytä SSID RSSI -kohtaisesti Wi‑Fi-valitsimessa."</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kun asetus on käytössä, datayhteys siirtyy helpommin Wi-Fistä matkapuhelinverkkoon, jos Wi-Fi-signaali on heikko."</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 00c02ce..bb5d2d8 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec audio Bluetooth LDAC : qualité de lecture"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Sélectionner le codec audio Bluetooth LDAC :\nQualité de lecture"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Diffusion : <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS par TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Si cette option est activée, essayez le DNS par TLS sur le port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Afficher les options pour la certification d\'affichage sans fil"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Détailler davantage les données Wi-Fi, afficher par SSID RSSI dans sélect. Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Si cette option est activée, le passage du Wi-Fi aux données cellulaires est forcé lorsque le signal Wi-Fi est faible"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index a5625e4..9238c21 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec audio Bluetooth LDAC : qualité de lecture"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Sélectionner le codec audio Bluetooth LDAC :\nQualité de lecture"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Diffusion : <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS sur TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Si l\'option est activée, essayez le protocole DNS sur TLS sur le port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Afficher les options de la certification de l\'affichage sans fil"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Détailler plus infos Wi-Fi, afficher par RSSI de SSID dans outil sélection Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Si cette option est activée, le passage du Wi-Fi aux données mobiles est forcé en cas de signal Wi-Fi faible."</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index e1ea694..29b1e2b 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Códec LDAC de audio por Bluetooth: calidade de reprodución"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Seleccionar códec LDAC de audio por Bluetooth:\ncalidade de reprodución"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Reprodución en tempo real: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS a través de TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Se se activa esta opción, téntase utilizar o método DNS a través de TLS no porto 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostra opcións para o certificado de visualización sen fíos"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nivel de rexistro da wifi, mostrar por SSID RSSI no selector de wifi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Cando estea activada esta función, a wifi será máis agresiva ao transferir a conexión de datos ao móbil cando o sinal wifi sexa feble"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 0e8dd47..cbaa414 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"બ્લૂટૂથ ઑડિઓ LDAC કોડેક: પ્લેબૅક ગુણવત્તા"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"બ્લૂટૂથ ઑડિઓ LDAC કોડેક પસંદ કરો:\nપ્લેબૅક ગુણવત્તા"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"સ્ટ્રીમિંગ: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS ઓવર TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"જો ચાલુ કરવામાં આવે, તો પોર્ટ 853 પરથી DNS ઓવર TLSનો પ્રયાસ કરો."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"વાયરલેસ ડિસ્પ્લે પ્રમાણપત્ર માટેના વિકલ્પો બતાવો"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"વાઇ-ફાઇ લોગિંગ સ્તર વધારો, વાઇ-ફાઇ પીકરમાં SSID RSSI દીઠ બતાવો"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"જ્યારે સક્ષમ કરેલ હોય, ત્યારે વાઇ-ફાઇ સિગ્નલ નબળું હોવા પર, વાઇ-ફાઇ વધુ ઝડપથી ડેટા કનેક્શનને મોબાઇલ પર મોકલશે"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 28bc68a..5d25d20 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -209,6 +209,10 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ब्लूटूथ ऑडियो LDAC कोडेक: प्लेबैक क्वालिटी"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ब्लूटूथ ऑडियो LDAC कोडेक चुनें:\nप्लेबैक क्वालिटी"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"चलाया जा रहा है: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <!-- no translation found for dns_tls (6773814174391131955) -->
+ <skip />
+ <!-- no translation found for dns_tls_summary (3692494150251071380) -->
+ <skip />
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"वायरलेस दिखाई देने के लिए प्रमाणन विकल्प दिखाएं"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"वाई-फ़ाई प्रवेश स्तर बढ़ाएं, वाई-फ़ाई पिकर में प्रति SSID RSSI दिखाएं"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"इसके सक्षम होने पर, जब वाई-फ़ाई संकेत कमज़ोर हों तो वाई-फ़ाई, डेटा कनेक्शन को मोबाइल पर ज़्यादा तेज़ी से भेजेगा"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index b5c5439..c134970 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodek za Bluetooth Audio LDAC: kvaliteta reprodukcije"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Odaberi kodek za Bluetooth Audio LDAC:\nkvaliteta reprodukcije"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Strujanje: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS putem TLS-a"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Ako je omogućeno, pokušava se upotrijebiti DNS putem TLS-a na priključku 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Prikaži opcije za certifikaciju bežičnog prikaza"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povećana razina prijave na Wi‑Fi, prikaz po SSID RSSI-ju u Biraču Wi‑Fi-ja"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Ako je omogućeno, Wi-Fi će aktivno prebacivati podatkovnu vezu mobilnoj mreži kada je Wi-Fi signal slab."</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 47d6c98..da206cb 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth LDAC hangkodek: lejátszási minőség"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth LDAC hangkodek kiválasztása:\nlejátszási minőség"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streamelés: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS-en keresztüli DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"TLS-en keresztüli DNS megkísérlése a 853-as porton, ha engedélyezve van."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Vezeték nélküli kijelző tanúsítványával kapcsolatos lehetőségek megjelenítése"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi-naplózási szint növelése, RSSI/SSID megjelenítése a Wi‑Fi-választóban"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Ha engedélyezi, a Wi-Fi agresszívebben fogja átadni az adatkapcsolatot a mobilhálózatnak gyenge Wi-Fi-jel esetén"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 6f11cd8..2365058 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth աուդիո LDAC կոդեկ՝ նվագարկման որակ"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Ընտրեք Bluetooth աուդիո LDAC կոդեկը՝\nնվագարկման որակ"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Հեռարձակում՝ <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS TLS-ի միջոցով"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Միացնելու դեպքում փորձել DNS-ը TLS-ի միջոցով միացք 853-ում:"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Ցույց տալ անլար էկրանի հավաստագրման ընտրանքները"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Բարձրացնել մակարդակը, Wi‑Fi ընտրիչում ամեն մի SSID-ի համար ցույց տալ RSSI"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Եթե այս գործառույթը միացված է, Wi-Fi-ի թույլ ազդանշանի դեպքում Wi‑Fi ինտերնետից բջջային ինտերնետի անցումը ավելի կտրուկ կկատարվի"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index b4a58bb..7e2848f 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC Audio Bluetooth: Kualitas Pemutaran"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Pilih Codec LDAC Audio Bluetooth:\nKualitas Pemutaran"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS melalui TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Jika diaktifkan, coba DNS melalui TLS pada port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Tampilkan opsi untuk sertifikasi layar nirkabel"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Tingkatkan level pencatatan log Wi-Fi, tampilkan per SSID RSSI di Pemilih Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Jika diaktifkan, Wi-Fi akan menjadi lebih agresif dalam mengalihkan sambungan data ke seluler saat sinyal Wi-Fi lemah"</string>
@@ -277,7 +279,7 @@
<string name="debug_layout_summary" msgid="2001775315258637682">"Tampilkan batas klip, margin, dll."</string>
<string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Paksa arah tata letak RTL"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Paksa arah tata letak layar RTL untuk semua lokal"</string>
- <string name="force_hw_ui" msgid="6426383462520888732">"Paksa perenderan GPU"</string>
+ <string name="force_hw_ui" msgid="6426383462520888732">"Paksa rendering GPU"</string>
<string name="force_hw_ui_summary" msgid="5535991166074861515">"Paksa penggunaan GPU untuk gambar 2d"</string>
<string name="force_msaa" msgid="7920323238677284387">"Paksa 4x MSAA"</string>
<string name="force_msaa_summary" msgid="9123553203895817537">"Aktifkan 4x MSAA dalam aplikasi OpenGL ES 2.0"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 29b1b5d..5f4d401 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth LDAC-hljóðkóðari: gæði spilunar"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Velja Bluetooth LDAC-hljóðkóðara:\ngæði spilunar"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streymi: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS yfir TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Ef kveikt er á þessu, reyna DNS yfir TLS á gátt 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Sýna valkosti fyrir vottun þráðlausra skjáa"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Auka skráningarstig Wi-Fi, sýna RSSI fyrir hvert SSID í Wi-Fi vali"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Þegar þetta er virkt mun Wi-Fi skipta hraðar yfir í farsímagagnatengingu þegar Wi-Fi-tenging er léleg"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 46004c3..439fea1 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC audio Bluetooth: qualità di riproduzione"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Seleziona il codec LDAC audio Bluetooth:\nQualità di riproduzione"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS tramite TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Se l\'opzione è attiva, prova DNS tramite TLS sulla porta 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostra opzioni per la certificazione display wireless"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumenta il livello di registrazione Wi-Fi, mostrando il SSID RSSI nel selettore Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Con questa impostazione attivata, il Wi-Fi è più aggressivo nel passare la connessione dati al cellulare, con segnale Wi-Fi basso"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 24f4684..435abbb 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec אודיו LDAC ל-Bluetooth: איכות נגינה"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"בחירת Codec אודיו LDAC ל-Bluetooth:\nאיכות נגינה"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"סטרימינג: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS באמצעות TLS (אבטחת שכבת התעבורה)"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"אם האפשרות מופעלת, יש לנסות DNS באמצעות TLS (אבטחת שכבת התעבורה) ביציאה 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"הצג אפשרויות עבור אישור של תצוגת WiFi"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"העלה את רמת הרישום של Wi‑Fi ביומן, הצג לכל SSID RSSI ב-Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"כשאפשרות זו מופעלת, Wi-Fi יתנהג בצורה אגרסיבית יותר בעת העברת חיבור הנתונים לרשת הסלולרית כשאות ה-Wi-Fi חלש."</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index bb3f5a9..308e97d8 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth オーディオ LDAC コーデック: 再生音質"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth オーディオ LDAC コーデックを選択:\n再生音質"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ストリーミング: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"この設定を有効にした場合、ポート 853 で DNS over TLS を試行します。"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ワイヤレスディスプレイ認証のオプションを表示"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fiログレベルを上げて、Wi-Fi選択ツールでSSID RSSIごとに表示します"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ON にすると、Wi-Fi の電波強度が弱い場合は強制的にモバイルデータ接続に切り替わるようになります"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 2280261..f455324 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth აუდიოს LDAC კოდეკის დაკვრის ხარისხი"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"აირჩიეთ Bluetooth აუდიოს LDAC კოდეკის\nდაკვრის ხარისხი"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"სტრიმინგი: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS-ის TLS-ის მეშვეობით გადაცემა"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"ჩართვის შემთხვევაში, DNS-ის TLS-ის მეშვეობით გადაცემის ცდა, პორტზე 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"უსადენო ეკრანის სერტიფიცირების ვარიანტების ჩვენება"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi-ს აღრიცხვის დონის გაზრდა, Wi‑Fi ამომრჩეველში ყოველ SSID RSSI-ზე ჩვენება"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ჩართვის შემთხვევაში, Wi‑Fi უფრო აქტიურად შეეცდება მობილურ ინტერნეტზე გადართვას, როცა Wi‑Fi სიგნალი სუსტია"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 03bd3cd..223a328 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth LDAC аудиокодегі: ойнату сапасы"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth LDAC аудиокодегін таңдау:\nойнату сапасы"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Трансляция: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS бойынша DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Қосулы болса, 853 портында DNS жүйесін TLS протоколы арқылы қосып көріңіз."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Сымсыз дисплей растау опцияларын көрсету"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi жур. тір. дең. арт., Wi‑Fi желісін таңдағышта әр SSID RSSI бойынша көрсету"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Wi‑Fi сигналы әлсіз болғанда, деректер байланысы мәжбүрлі түрде мобильдік желіге ауысады"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 6414141..873d15a 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"កូឌិកប្រភេទ LDAC នៃសំឡេងប៊្លូធូស៖ គុណភាពចាក់សំឡេង"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ជ្រើសរើសកូឌិកប្រភេទ LDAC នៃសំឡេងប៊្លូធូស៖\nគុណភាពចាក់សំឡេង"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"កំពុងចាក់៖ <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS តាមរយៈ TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"ប្រសិនបើបើក វានឹងព្យាយាមប្រើ DNS តាមរៈ TLS នៅលើរន្ធ 853។"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"បង្ហាញជម្រើសសម្រាប់វិញ្ញាបនបត្របង្ហាញឥតខ្សែ"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"បង្កើនកម្រិតកំណត់ហេតុវ៉ាយហ្វាយបង្ហាញក្នុង SSID RSSI ក្នុងកម្មវិធីជ្រើសវ៉ាយហ្វាយ"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"នៅពេលដែលបើក នោះ Wi‑Fi នឹងផ្តល់ការតភ្ជាប់ទិន្នន័យយ៉ាងគំហុកទៅបណ្តាញទូរសព្ទចល័ត នៅពេលរលកសញ្ញា Wi‑Fi ចុះខ្សោយ។"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index d108dd6..8d9a7e1 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೊ LDAC ಕೋಡೆಕ್: ಪ್ಲೇಬ್ಯಾಕ್ ಗುಣಮಟ್ಟ"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೊ LDAC ಕೋಡೆಕ್:\nಪ್ಲೇಬ್ಯಾಕ್ ಗುಣಮಟ್ಟ ಆಯ್ಕೆ ಮಾಡಿ"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ಸ್ಟ್ರೀಮಿಂಗ್: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS ಮೇಲೆ DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"ಸಕ್ರಿಯಗೊಂಡರೆ, ಪೋರ್ಟ್ 853 ನಲ್ಲಿ TLS ಮೇಲೆ DNS ಅನ್ನು ಪ್ರಯತ್ನಿಸಿ."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ವೈರ್ಲೆಸ್ ಪ್ರದರ್ಶನ ಪ್ರಮಾಣೀಕರಣಕ್ಕಾಗಿ ಆಯ್ಕೆಗಳನ್ನು ತೋರಿಸು"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi ಲಾಗಿಂಗ್ ಮಟ್ಟನ್ನು ಹೆಚ್ಚಿಸಿ, Wi‑Fi ಆಯ್ಕೆಯಲ್ಲಿ ಪ್ರತಿಯೊಂದು SSID RSSI ತೋರಿಸಿ"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ಇದು ಸಕ್ರಿಯಗೊಂಡರೆ, ವೈ-ಫೈ ಸಿಗ್ನಲ್ ದುರ್ಬಲವಾಗಿದ್ದಾಗ, ಮೊಬೈಲ್ಗೆ ಡೇಟಾ ಸಂಪರ್ಕವನ್ನು ಹಸ್ತಾಂತರಿಸುವಲ್ಲಿ ವೈ-ಫೈ ಹೆಚ್ಚು ಆಕ್ರಮಣಕಾರಿಯಾಗಿರುತ್ತದೆ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index ec03fb1..4e3bc33 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"블루투스 오디오 LDAC 코덱: 재생 품질"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"블루투스 오디오 LDAC 코덱 선택:\n재생 품질"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"스트리밍: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS를 통한 DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"사용 설정한 경우, 포트 853에서 TLS를 통해 DNS를 시도합니다."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"무선 디스플레이 인증서 옵션 표시"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi 로깅 수준을 높이고, Wi‑Fi 선택도구에서 SSID RSSI당 값을 표시합니다."</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"사용 설정하면 Wi-Fi 신호가 약할 때 데이터 연결을 Wi-Fi에서 모바일 네트워크로 더욱 적극적으로 핸드오버합니다."</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 883811d..09edefe 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth аудио LDAC кодеги: Ойнотуу сапаты"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth аудио LDAC кодегин тандаңыз:\nОйнотуу сапаты"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Трансляция: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS аркылуу DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Эгер иштетилсе, DNS сервери TLS аркылуу 853-оюкчага аракет кылсын."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Зымсыз дисплейди сертификатто мүмкүнчүлүктөрүн көргөзүү"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fi Кармагычта Wi‑Fi протокол деңгээлин жогорулатуу жана ар бир SSID RSSI үчүн көрсөтүү."</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Иштетилсе, Wi-Fi байланышы үзүл-кесил болуп жатканда, Wi-Fi тармагы туташууну мобилдик Интернетке өжөрлүк менен өткөрүп берет"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 464b490..f1c92b07 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS ຜ່ານ TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"ຫາກເປີດໃຊ້, ລະບົບຈະພະຍາຍາມໃຊ້ DNS ຜ່ານ TLS ຢູ່ຜອດ 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ສະແດງໂຕເລືອກສຳລັບການສະແດງການຮັບຮອງລະບົບໄຮ້ສາຍ"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"ເພີ່ມລະດັບການເກັບປະຫວັດ Wi‑Fi, ສະແດງຕໍ່ SSID RSSI ໃນ Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ເມື່ອເປີດໃຊ້ແລ້ວ, Wi-Fi ຈະສົ່ງຜ່ານການເຊື່ອມຕໍ່ຂໍ້ມູນໄປຫາເຄືອຂ່າຍມືຖືເມື່ອສັນຍານ Wi-Fi ອ່ອນ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index fd3bc18..f888504 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"„Bluetooth“ garso LDAC kodekas: atkūrimo kokybė"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Pasirinkite „Bluetooth“ garso LDAC kodeką:\natkūrimo kokybė"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Srautinis perdavimas: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS naudojant TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Jei parinktis įgalinta, bandykite pateikti DNS užklausą naudojant TLS 853 prievadu."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Rodyti belaidžio rodymo sertifikavimo parinktis"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Padidinti „Wi‑Fi“ įrašymo į žurnalą lygį, rodyti SSID RSSI „Wi-Fi“ rinkiklyje"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Jei ši parinktis įgalinta, „Wi‑Fi“ agresyviau perduos duomenų ryšiu į mobiliojo ryšio tinklą, kai „Wi‑Fi“ signalas silpnas"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index a6d308a..dbe13b5 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth audio LDAC kodeks: atskaņošanas kvalitāte"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Atlasīt Bluetooth audio LDAC kodeku:\natskaņošanas kvalitāte"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Straumēšana: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS, izmantojot TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Ja opcija ir iespējota, tiek mēģināts pieprasīt DNS, izmantojot TLS portā 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Rādīt bezvadu attēlošanas sertifikācijas iespējas"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Palieliniet Wi‑Fi reģistrēšanas līmeni; rādīt katram SSID RSSI Wi‑Fi atlasītājā."</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Ja opcija ir iespējota un Wi‑Fi signāls ir vājš, datu savienojuma pāreja no Wi-Fi uz mobilo tīklu tiks veikta agresīvāk."</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 8882cb7..0ebb44d 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Кодек за LDAC-аудио преку Bluetooth: квалитет на репродукција"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Изберете кодек за LDAC-аудио преку Bluetooth:\nКвалитет на репродукција"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Емитување: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS преку TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Ако е овозможено, обидете се со DNS преку TLS на портата 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Покажи ги опциите за безжичен приказ на сертификат"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Зголеми Wi‑Fi ниво на пријавување, прикажи по SSID RSSI во Wi‑Fi бирач"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Кога е овозможено, Wi-Fi ќе биде поагресивна при предавање на интернет-врската на мобилната мрежа при слаб сигнал на Wi-Fi"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 8f711b3..a2a10f2 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth ഓഡിയോ LDAC കോഡെക്: പ്ലേബാക്ക് നിലവാരം"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth ഓഡിയോ LDAC കോഡെക് തിരഞ്ഞെടുക്കുക:\nപ്ലേബാക്ക് നിലവാരം"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"സ്ട്രീമിംഗ്: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS വഴിയുള്ള DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"പ്രവർത്തനക്ഷമമാക്കിയിട്ടുണ്ടെങ്കിൽ, പോർട്ട് 853-ലെ TLS വഴി DNS-ന് ശ്രമിക്കുക."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"വയർലെസ് ഡിസ്പ്ലേ സർട്ടിഫിക്കേഷനായി ഓപ്ഷനുകൾ ദൃശ്യമാക്കുക"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"വൈഫൈ പിക്കറിൽ ഓരോ SSID RSSI പ്രകാരം കാണിക്കാൻ വൈഫൈ ലോഗിംഗ് നില വർദ്ധിപ്പിക്കുക"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"പ്രവർത്തനക്ഷമമായിരിക്കുമ്പോൾ, വൈഫൈ സിഗ്നൽ കുറവായിരിക്കുന്ന സമയത്ത് മൊബൈലിലേക്ക് ഡാറ്റ കണക്ഷൻ വഴി കൈമാറുന്നതിൽ വൈഫൈ കൂടുതൽ സക്രിയമായിരിക്കും"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 2e1f5e6..0990ecf 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Аудио LDAC Кодлогч: Тоглуулагчийн чанар"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth Аудио LDAC Кодлогч сонгох:\nТоглуулагчийн чанар"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Дамжуулж байна: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS дээрх DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Идэвхжүүлсэн тохиолдолд TLS-р DNS-г 853-р портод оролдоно уу."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Утасгүй дэлгэцийн сертификатын сонголтыг харуулах"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi лог-н түвшинг нэмэгдүүлэх, Wi‑Fi Сонгогч дээрх SSID-д ногдох RSSI-г харуулах"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Идэвхжүүлсэн үед Wi‑Fi холболт сул байх үед дата холболтыг мобайлд шилжүүлэхэд илүү идэвхтэй байх болно"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 63e2e87..42ee581 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ब्लूटूथ ऑडिओ LDAC कोडेक: प्लेबॅक गुणवत्ता"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ब्लूटूथ ऑडिओ LDAC कोडेक निवडा:\nप्लेबॅक गुणवत्ता"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"स्ट्रीमिंग: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS ओव्हर TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"सुरू केल्यास, पोर्ट 853 वर DNS ओव्हर TLS करण्याचा प्रयत्न करा."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"वायरलेस डिस्प्ले प्रमाणिकरणाचे पर्याय दाखवा"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"वाय-फाय लॉगिंग स्तर वाढवा, वाय-फाय सिलेक्टरमध्ये प्रति SSID RSSI दर्शवा"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"सक्षम केले असताना, वाय-फाय सिग्नल कमी असताना, मोबाइलकडे डेटा कनेक्शन सोपवण्यासाठी वाय-फाय अधिक अॅग्रेसिव्ह असेल."</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 9d81cea..857093a 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC Audio Bluetooth: Kualiti Main Balik"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Pilih Codec LDAC Audio Bluetooth:\nKualiti Main Balik"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Penstriman: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS di atas TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Jika di dayakan, cuba DNS di atas TLS pada port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Tunjukkan pilihan untuk pensijilan paparan wayarles"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Tingkatkan tahap pengelogan Wi-Fi, tunjuk setiap SSID RSSI dalam Pemilih Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Apabila didayakan, Wi-Fi akan menjadi lebih agresif dalam menyerahkan sambungan data ke mudah alih, apabila isyarat Wi-Fi rendah"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index aa241a9..7d166b4 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ဘလူးတုသ်အသံ LDAC ကိုးဒက်ခ်− နားထောင်ရန် အရည်အသွေး"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ဘလူးတုသ်အသံ LDAC ကိုးဒက်ခ်ကို ရွေးပါ−\nနားထောင်ရန် အရည်အသွေး"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"တိုက်ရိုက်လွှင့်နေသည်− <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"ဖွင့်ထားပါက DNS over TLS ကို ပို့တ် ၈၅၃ တွင် စမ်းကြည့်ပါ။"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ကြိုးမဲ့ အခင်းအကျင်း အသိအမှတ်ပြုလက်မှတ်အတွက် ရွေးချယ်စရာများပြရန်"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi မှတ်တမ်းတင်ခြင်း နှုန်းအားမြင့်ကာ၊ Wi‑Fi ရွေးရာတွင် SSID RSSI ဖြင့်ပြပါ"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ဖွင့်ထားပါက Wi‑Fi လွှင့်အား နည်းချိန်တွင် Wi‑Fi မှ မိုဘိုင်းသို့ ဒေတာချိတ်ဆက်မှုကို လွှဲပြောင်းရာ၌ ပိုမိုထိရောက်ပါသည်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index daf0051..f88df54 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"LDAC-kodek for Bluetooth-lyd: Avspillingskvalitet"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Velg LDAC-kodek for Bluetooth-lyd:\nAvspillingskvalitet"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Strømming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Hvis det er slått på, forsøk DNS over TLS på port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Vis alternativer for sertifisering av trådløs skjerm"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Øk Wi-Fi-loggenivå – vis per SSID RSSI i Wi-Fi-velgeren"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Hvis dette slås på, overfører Wi-Fi-nettverket datatilkoblingen til mobil mer aggressivt når Wi-Fi-signalet er svakt"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 105c442..a3875a6 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ब्लुटुथ अडियो LDAC कोडेक: प्लेब्याक गुणस्तर"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ब्लुटुथ अडियो LDAC कोडेक चयन गर्नुहोस्:\nप्लेब्याक गुणस्तर"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"स्ट्रिमिङ: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS मा DNS को प्रयोग"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"सक्षम पारिएको खण्डमा पोर्ट ८५३ मा TLS मा DNS प्रयोग गर्ने अनुरोध गर्नुहोस्।"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ताररहित प्रदर्शन प्रमाणीकरणका लागि विकल्पहरू देखाउनुहोस्"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fi लग स्तर बढाउनुहोस्, Wi-Fi चयनकर्तामा प्रति SSID RSSI देखाइन्छ"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"सक्षम गरिएको अवस्थामा, Wi-Fi सिग्नल न्यून हुँदा, Wi-Fi ले बढी आक्रामक ढंगले मोबाइलमा डेटा जडान हस्तान्तरण गर्नेछ"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index d8ae320..4d0d7fe 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -173,8 +173,8 @@
<string name="enable_adb" msgid="7982306934419797485">"USB-foutopsporing"</string>
<string name="enable_adb_summary" msgid="4881186971746056635">"Foutopsporingsmodus bij USB-verbinding"</string>
<string name="clear_adb_keys" msgid="4038889221503122743">"Autorisatie USB-foutopsporing intrekken"</string>
- <string name="bugreport_in_power" msgid="7923901846375587241">"Snelle link naar foutenrapport"</string>
- <string name="bugreport_in_power_summary" msgid="1778455732762984579">"Een knop in het voedingsmenu weergeven om een foutenrapport te maken"</string>
+ <string name="bugreport_in_power" msgid="7923901846375587241">"Snelle link naar bugrapport"</string>
+ <string name="bugreport_in_power_summary" msgid="1778455732762984579">"Een knop in het voedingsmenu weergeven om een bugrapport te maken"</string>
<string name="keep_screen_on" msgid="1146389631208760344">"Stand-by"</string>
<string name="keep_screen_on_summary" msgid="2173114350754293009">"Scherm gaat nooit uit tijdens het opladen"</string>
<string name="bt_hci_snoop_log" msgid="3340699311158865670">"Snoop-logbestand voor Bluetooth-HCI inschakelen"</string>
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"LDAC-codec voor Bluetooth-audio: afspeelkwaliteit"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"LDAC-codec voor Bluetooth-audio selecteren:\nafspeelkwaliteit"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS via TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Als deze optie is ingeschakeld, wordt geprobeerd om DNS via TLS uit te voeren via poort 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Opties weergeven voor certificering van draadloze weergave"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Logniveau voor wifi verhogen, weergeven per SSID RSSI in wifi-kiezer"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Indien ingeschakeld, is wifi agressiever bij het overgeven van de gegevensverbinding aan mobiel wanneer het wifi-signaal zwak is"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index b54d296..56e361f 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ਬਲੂਟੁੱਥ ਔਡੀਓ LDAC ਕੋਡੇਕ: ਪਲੇਬੈਕ ਗੁਣਵੱਤਾ"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ਬਲੂਟੁੱਥ ਔਡੀਓ LDAC ਕੋਡੇਕ ਚੁਣੋ:\nਪਲੇਬੈਕ ਗੁਣਵੱਤਾ"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ਸਟ੍ਰੀਮਿੰਗ: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS \'ਤੇ DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"ਚਾਲੂ ਕੀਤੇ ਜਾਣ \'ਤੇ, ਪੋਰਟ 853 \'ਤੇ TLS \'ਤੇ DNS ਵਰਤਣ ਦੀ ਬੇਨਤੀ ਕਰੋ।"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ਵਾਇਰਲੈੱਸ ਡਿਸਪਲੇ ਪ੍ਰਮਾਣੀਕਰਨ ਲਈ ਚੋਣਾਂ ਪ੍ਰਦਰਸ਼ਿਤ ਕਰੋ"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"ਵਾਈ‑ਫਾਈ ਲੌਗਿੰਗ ਪੱਧਰ ਵਧਾਓ, ਵਾਈ‑ਫਾਈ Picker ਵਿੱਚ ਪ੍ਰਤੀ SSID RSSI ਦਿਖਾਓ"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ਜਦੋਂ ਯੋਗ ਬਣਾਇਆ ਹੋਵੇ, ਤਾਂ ਵਾਈ‑ਫਾਈ ਸਿਗਨਲ ਘੱਟ ਹੋਣ \'ਤੇ ਵਾਈ‑ਫਾਈ ਡਾਟਾ ਕਨੈਕਸ਼ਨ ਮੋਬਾਈਲ ਨੂੰ ਹੈਂਡ ਓਵਰ ਕਰਨ ਵਿੱਚ ਵੱਧ ਆਕਰਮਣਸ਼ੀਲ ਹੋਵੇਗਾ।"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 4810279..51c8505 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodek dźwięku Bluetooth LDAC: jakość odtwarzania"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Wybierz kodek dźwięku Bluetooth LDAC:\njakość odtwarzania"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Strumieniowe przesyłanie danych: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS przez TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Po włączeniu wypróbuj komunikację DNS przez TLS przez port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Pokaż opcje certyfikacji wyświetlacza bezprzewodowego"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Zwiększ poziom rejestrowania Wi‑Fi, pokazuj według RSSI SSID w selektorze Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Po włączeniu połączenie danych będzie bardziej agresywnie przełączać się z Wi-Fi na sieć komórkową przy słabym sygnale Wi-Fi"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 7a080a2..6b2a5ca 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec de áudio Bluetooth LDAC: qualidade de reprodução"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecionar codec de áudio Bluetooth LDAC:\nqualidade de reprodução"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS por TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Se essa opção estiver ativada, tente o DNS por TLS na porta 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opções de certificação de Display sem fio"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nível de registro do Wi-Fi; mostrar conforme o RSSI de SSID na Seleção de Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Quando ativada, o Wi-Fi será mais agressivo em passar a conexão de dados para móvel, quando o sinal de Wi-Fi estiver fraco"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 9229e13..1a6f0c6 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC de áudio Bluetooth: qualidade de reprodução"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecionar codec LDAC de áudio Bluetooth:\nQualidade de reprodução"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmissão em fluxo contínuo: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS através de TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Se a opção estiver ativada, tente efetuar DNS através de TLS na porta 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opções da certificação de display sem fios"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nível de reg. de Wi-Fi, mostrar por RSSI de SSID no Selec. de Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Se estiver ativado, o Wi-Fi será mais agressivo ao transmitir a lig. de dados para a rede móvel quando o sinal Wi-Fi estiver fraco"</string>
@@ -357,9 +359,9 @@
<string name="battery_info_status_discharging" msgid="310932812698268588">"Não está a carregar"</string>
<string name="battery_info_status_not_charging" msgid="8523453668342598579">"Ligada à corrente, não é possível carregar neste momento"</string>
<string name="battery_info_status_full" msgid="2824614753861462808">"Completo"</string>
- <string name="disabled_by_admin_summary_text" msgid="6750513964908334617">"Controlado pelo administrador"</string>
- <string name="enabled_by_admin" msgid="5302986023578399263">"Ativada pelo administrador"</string>
- <string name="disabled_by_admin" msgid="8505398946020816620">"Desativada pelo administrador"</string>
+ <string name="disabled_by_admin_summary_text" msgid="6750513964908334617">"Controlado pelo gestor"</string>
+ <string name="enabled_by_admin" msgid="5302986023578399263">"Ativada pelo gestor"</string>
+ <string name="disabled_by_admin" msgid="8505398946020816620">"Desativada pelo gestor"</string>
<string name="disabled" msgid="9206776641295849915">"Desativada"</string>
<string name="external_source_trusted" msgid="2707996266575928037">"Autorizada"</string>
<string name="external_source_untrusted" msgid="2677442511837596726">"Não autorizada"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 7a080a2..6b2a5ca 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec de áudio Bluetooth LDAC: qualidade de reprodução"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecionar codec de áudio Bluetooth LDAC:\nqualidade de reprodução"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS por TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Se essa opção estiver ativada, tente o DNS por TLS na porta 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opções de certificação de Display sem fio"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nível de registro do Wi-Fi; mostrar conforme o RSSI de SSID na Seleção de Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Quando ativada, o Wi-Fi será mais agressivo em passar a conexão de dados para móvel, quando o sinal de Wi-Fi estiver fraco"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index df34d58..7be64e4 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codecul LDAC audio pentru Bluetooth: calitatea redării"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selectați codecul LDAC audio pentru Bluetooth:\ncalitatea redării"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmitere în flux: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS prin TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Dacă opțiunea este activată, încercați DNS prin TLS pe portul 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Afișați opțiunile pentru certificarea Ecran wireless"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Măriți niv. de înr. prin Wi‑Fi, afișați în fcț. de SSID RSSI în Selectorul Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Când este activată, Wi-Fi va fi mai agresivă la predarea conexiunii de date către rețeaua mobilă când semnalul Wi-Fi este slab"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 3752cb3..d88adea 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Аудиокодек LDAC для Bluetooth: качество воспроизведения"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Аудиокодек LDAC для Bluetooth:\nкачество воспроизведения"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Потоковая передача: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS по TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Если функция включена, соединение с DNS будет выполняться по TLS через порт 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Показывать параметры сертификации беспроводных мониторов"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"При выборе Wi‑Fi указывать в журнале RSSI для каждого SSID"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Принудительно переключаться на мобильную сеть, если сигнал Wi-Fi слабый"</string>
@@ -293,7 +295,7 @@
<string name="app_process_limit_title" msgid="4280600650253107163">"Лимит фоновых процессов"</string>
<string name="show_all_anrs" msgid="28462979638729082">"Все ANR"</string>
<string name="show_all_anrs_summary" msgid="641908614413544127">"Уведомлять о том, что приложение не отвечает"</string>
- <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Показывать предупреждения канала передачи оповещения"</string>
+ <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Показывать предупреждения канала передачи уведомлений"</string>
<string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Показывать предупреждение о новых уведомлениях приложения вне допустимого канала"</string>
<string name="force_allow_on_external" msgid="3215759785081916381">"Разрешить сохранение на внешние накопители"</string>
<string name="force_allow_on_external_summary" msgid="3640752408258034689">"Разрешить сохранение приложений на внешних накопителях (независимо от значений в манифесте)"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 36fd952..db01eea 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"බ්ලූටූත් ශ්රව්ය LDAC පසුධාවන ගුණත්වය"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"බ්ලූටූත් ශ්රව්ය LDAC කොඩෙක් තෝරන්න:\nපසුධාවන ගුණත්වය"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ප්රවාහ කරමින්: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS හරහා DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"සබල නම්, 853 තොට මත TLS හරහා DNS උත්සාහ කරන්න."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"නොරැහැන් සංදර්ශක සහතිකය සඳහා විකල්ප පෙන්වන්න"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi ලොග් මට්ටම වැඩි කරන්න, Wi‑Fi තෝරනයෙහි SSID RSSI අනුව පෙන්වන්න"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"සබල විට Wi‑Fi සිග්නලය අඩු විට Wi‑Fi දත්ත සම්බන්ධතාවය ජංගම වෙත භාර දීමට වඩා ආක්රමණික වේ"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 2eea457..fc7e1c3 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodek LDAC Bluetooth Audio: Kvalita prehrávania"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Vybrať kodek LDAC Bluetooth Audio:\nKvalita prehrávania"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streamovanie: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS cez TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Ak túto možnosť aktivujete, zariadenie sa bude pripájať k serverom DNS pomocou protokolu TLS na porte 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Zobraziť možnosti certifikácie bezdrôtového zobrazenia"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Zvýšiť úroveň denníkov Wi‑Fi, zobrazovať podľa SSID RSSI pri výbere siete Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Keď túto možnosť zapnete, Wi‑Fi bude agresívnejšie odovzdávať dátové pripojenie na mobilnú sieť vtedy, keď bude slabý signál Wi‑Fi"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index e7e6212..07475d3 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Zvočni kodek LDAC za Bluetooth: kakovost predvajanja"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Izberi zvočni kodek LDAC za Bluetooth:\nKakovost predvajanja"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Pretočno predvajanje: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS s šifriranjem TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Če je ta možnost omogočena, poskusi vzpostaviti povezavo DNS s šifriranjem TLS na vratih 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Pokaži možnosti za potrdilo brezžičnega zaslona"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povečaj raven zapis. dnev. za Wi-Fi; v izbir. Wi‑Fi-ja pokaži glede na SSID RSSI"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Če je ta možnost omogočena, Wi-Fi odločneje preda podatkovno povezavo mobilnemu omrežju, ko je signal Wi-Fi šibek."</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index f557d81..4fcfc5c 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodeku LDAC i audios së Bluetooth-it: Cilësia e luajtjes"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Zgjidh kodekun LDAC të audios së Bluetooth-it:\nCilësia e luajtjes"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmetimi: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS nëpërmjet protokollit TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Nëse është aktivizuar, provo DNS-në nëpërmjet protokollit TLS në portën 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Shfaq opsionet për certifikimin e ekranit valor"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Rrit nivelin regjistrues të Wi‑Fi duke shfaqur SSID RSSI-në te Zgjedhësi i Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kur ky funksion aktivizohet, Wi‑Fi bëhet më agresiv në kalimin e lidhjes së të dhënave te rrjeti celular, në rastet kur sinjali Wi‑Fi është i dobët"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 1c0fb24..e591aa1 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth аудио кодек LDAC: квалитет репродукције"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Изаберите Bluetooth аудио кодек LDAC:\nквалитет репродукције"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Стримовање: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS преко TLS-а"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Ако је омогућено, пробајte DNS преко TLS-а на порту 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Приказ опција за сертификацију бежичног екрана"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Повећава ниво евидентирања за Wi‑Fi. Приказ по SSID RSSI-у у бирачу Wi‑Fi мреже"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Кад се омогући, Wi‑Fi ће бити агресивнији при пребацивању мреже за пренос података на мобилну ако је Wi‑Fi сигнал слаб"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index c233bec..cb86b5b 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth-ljud via LDAC-kodek: uppspelningskvalitet"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Välj Bluetooth-ljud via LDAC-kodek:\nuppspelningskvalitet"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS via TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Om inställningen är aktiverad görs försök att använda DNS via TLS på port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Visa certifieringsalternativ för Wi-Fi-skärmdelning"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Öka loggningsnivån för Wi-Fi, visa per SSID RSSI i Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"När funktionen har aktiverats kommer dataanslutningen lämnas över från Wi-Fi till mobilen på ett aggressivare sätt när Wi-Fi-signalen är svag"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 700153b..756254b 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodeki ya LDAC ya Sauti ya Bluetooth: Ubora wa Kucheza"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Chagua Kodeki ya LDAC ya Sauti ya Bluetooth:\nUbora wa Kucheza"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Kutiririsha: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS kupitia TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Ukiwasha mipangilio hii, jaribu kutumia DNS kupitia TLS kwenye mlango wa 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Onyesha chaguo za cheti cha kuonyesha pasiwaya"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Ongeza hatua ya uwekaji kumbukumbu ya Wi-Fi, onyesha kwa kila SSID RSSI kwenye Kichukuzi cha Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Ikiwashwa, Wi-Fi itakabidhi kwa hima muunganisho wa data kwa mtandao wa simu, wakati mtandao wa Wi-Fi si thabiti"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 5ea41c4..e31e3e0 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"புளூடூத் ஆடியோ LDAC கோடெக்: வீடியோவின் தரம்"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"புளூடூத் ஆடியோ LDAC கோடெக்கைத் தேர்ந்தெடுக்கவும்:\nவீடியோவின் தரம்"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ஸ்ட்ரீமிங்: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS வழியாக DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"இயக்கப்பட்டிருந்தால், போர்ட் 853 இல் TLS வழியாக DNSஐ முயலவும்."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"வயர்லெஸ் காட்சி சான்றுக்கான விருப்பங்களைக் காட்டு"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wifi நுழைவு அளவை அதிகரித்து, வைஃபை தேர்வியில் ஒவ்வொன்றிற்கும் SSID RSSI ஐ காட்டுக"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"இயக்கப்பட்டதும், வைஃபை சிக்னல் குறையும் போது, வைஃபை முழுமையாக ஒத்துழைக்காமல் இருந்தால் மொபைல் தரவிற்கு மாறும்"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 7daa89e..0c28ef2 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"బ్లూటూత్ ఆడియో LDAC కోడెక్: ప్లేబ్యాక్ నాణ్యత"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"బ్లూటూత్ ఆడియో LDAC కోడెక్ని ఎంచుకోండి:\nప్లేబ్యాక్ నాణ్యత"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ప్రసారం చేస్తోంది: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLSపై DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"ప్రారంభించబడితే, పోర్ట్ 853లో TLSపై DNSని ప్రయత్నించండి."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"వైర్లెస్ ప్రదర్శన సర్టిఫికెట్ కోసం ఎంపికలను చూపు"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi ఎంపికలో SSID RSSI ప్రకారం చూపబడే Wi‑Fi లాగింగ్ స్థాయిని పెంచండి"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ప్రారంభించబడినప్పుడు, Wi‑Fi సిగ్నల్ బలహీనంగా ఉంటే డేటా కనెక్షన్ను మొబైల్కి మార్చేలా Wi‑Fi చురుగ్గా వ్యవహరిస్తుంది"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 37fba990..5ff39b6 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC: คุณภาพการเล่น"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"เลือกตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC:\nคุณภาพการเล่น"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"สตรีมมิง: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS ผ่าน TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"หากเปิดใช้แล้ว ให้ลองใช้ DNS ผ่าน TLS บนพอร์ต 853"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"แสดงตัวเลือกสำหรับการรับรองการแสดงผล แบบไร้สาย"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"เพิ่มระดับการบันทึก Wi‑Fi แสดงต่อ SSID RSSI ในตัวเลือก Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"เมื่อเปิดใช้แล้ว Wi-Fi จะส่งผ่านการเชื่อมต่อข้อมูลไปยังเครือข่ายมือถือเมื่อสัญญาณ Wi-Fi อ่อน"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 25227a3..6021d79 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Audio LDAC Codec ng Bluetooth: Kalidad ng Pag-playback"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Piliin ang Audio LDAC Codec ng Bluetooth:\nKalidad ng Pag-playback"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS sa pamamagitan ng TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Kung naka-enable, subukan ang DNS sa pamamagitan ng TLS sa port 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Ipakita ang mga opsyon para sa certification ng wireless display"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Pataasin ang antas ng Wi‑Fi logging, ipakita sa bawat SSID RSSI sa Wi‑Fi Picker"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kapag na-enable, magiging mas agresibo ang Wi‑Fi sa paglipat sa koneksyon ng mobile data kapag mahina ang signal ng Wi‑Fi"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 893d8b3..367f84b 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Ses LDAC Codec\'i: Oynatma Kalitesi"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth Ses LDAC Codec\'ini Seçin:\nOynatma Kalitesi"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Akış: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS üzerinden DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Etkinleştirildiyse, 853 numaralı bağlantı noktasında TLS üzerinden DNS\'yi deneyin."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Kablosuz ekran sertifikası seçeneklerini göster"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Kablosuz günlük kaydı seviyesini artır. Kablosuz Seçici\'de her bir SSID RSSI için göster."</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Etkinleştirildiğinde, kablosuz ağ sinyali zayıfken veri bağlantısının mobil ağa geçirilmesinde daha agresif olunur"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index f3ed643..2693002 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Кодек для аудіо Bluetooth LDAC: якість відтворення"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Вибрати кодек для аудіо Bluetooth LDAC:\nякість відтворення"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Трансляція: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS через протокол TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Якщо ввімкнено, спробуйте DNS через протокол TLS у порту 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Показати параметри сертифікації бездротового екрана"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Показувати в журналі RSSI для кожного SSID під час вибору Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Примусово перемикатися на мобільну мережу, коли сигнал Wi-Fi слабкий"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 550280d..782bb6f 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"بلوٹوتھ آڈیو LDAC کوڈیک: پلے بیک کا معیار"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"بلوٹوتھ آڈیو LDAC کوڈیک منتخب کریں:\nپلے بیک کا معیار"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"سلسلہ بندی: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS پر DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"فعال ہونے پر پورٹ 853 پر TLS پر DNS استعمال کرنے کی کوشش کریں۔"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"وائرلیس ڈسپلے سرٹیفیکیشن کیلئے اختیارات دکھائیں"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi لاگنگ لیول میں اضافہ کریں، Wi‑Fi منتخب کنندہ میں فی SSID RSSI دکھائیں"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"فعال کئے جانے پر، جب Wi‑Fi سگنل کمزور ہوگا، تو Wi‑Fi موبائل پر ڈیٹا کنکشن بھیجنے کیلئے مزید جارحانہ کارروائی کرے گا"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 055613b..95e4455 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"LDAC audiokodeki bilan ijro etish sifati (Bluetooth orqali)"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"LDAC audiokodeki:\nijro sifati"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Translatsiya: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"TLS orqali DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Yoqilgan bo‘lsa, TLS orqali DNS serverini 853-port yordamida sozlab ko‘ring"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Simsiz monitorlarni sertifikatlash parametrini ko‘rsatish"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fi ulanishini tanlashda har bir SSID uchun jurnalda ko‘rsatilsin"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Agar ushbu funksiya yoqilsa, Wi-Fi signali past bo‘lganda internetga ulanish majburiy ravishda mobil internetga o‘tkaziladi"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index bc5df8f..8beb819 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC âm thanh Bluetooth: Chất lượng phát lại"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Chọn Codec LDAC âm thanh Bluetooth:\nChất lượng phát lại"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Truyền trực tuyến: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"DNS qua TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Nếu được bật, hãy thử DNS qua TLS trên cổng 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Hiển thị tùy chọn chứng nhận hiển thị không dây"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Tăng mức ghi nhật ký Wi‑Fi, hiển thị mỗi SSID RSSI trong bộ chọn Wi‑Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Khi được bật, Wi‑Fi sẽ tích cực hơn trong việc chuyển vùng kết nối dữ liệu sang mạng di động khi tín hiệu Wi‑Fi yếu"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/arrays.xml b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
index 3a80e7b..dc24afd 100644
--- a/packages/SettingsLib/res/values-zh-rCN/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
@@ -24,7 +24,7 @@
<item msgid="1922181315419294640"></item>
<item msgid="8934131797783724664">"正在扫描..."</item>
<item msgid="8513729475867537913">"正在连接..."</item>
- <item msgid="515055375277271756">"正在进行身份验证..."</item>
+ <item msgid="515055375277271756">"正在验证身份…"</item>
<item msgid="1943354004029184381">"正在获取IP地址..."</item>
<item msgid="4221763391123233270">"已连接"</item>
<item msgid="624838831631122137">"已暂停"</item>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 4bbc976..094bb90 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"蓝牙音频 LDAC 编解码器:播放质量"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"选择蓝牙音频 LDAC 编解码器:\n播放质量"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"正在流式传输:<xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"通过传输层安全协议 (TLS) 执行 DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"如果启用,即可在端口 853 上尝试“通过传输层安全协议 (TLS) 执行 DNS”。"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"显示无线显示认证选项"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"提升WLAN日志记录级别(在WLAN选择器中显示每个SSID的RSSI)"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"开启此设置后,系统会在 WLAN 信号较弱时,主动将网络模式从 WLAN 网络切换到移动数据网络"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index e70b63b..c489850a 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"藍牙音訊 LDAC 編解碼器:播放品質"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"選擇藍牙音訊 LDAC 編解碼器:\n播放品質"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"正在串流:<xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"透過傳輸層安全性 (TLS) 執行網域名稱系統 (DNS)"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"如果啟用這個選項,即可在連接埠 853 上嘗試「透過傳輸層安全性 (TLS) 執行網域名稱系統 (DNS)」。"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"顯示無線螢幕分享認證的選項"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"讓 Wi‑Fi 記錄功能升級,在 Wi‑Fi 選擇器中依每個 SSID RSSI 顯示 Wi‑Fi 詳細紀錄"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"啟用後,Wi-Fi 連線會在訊號不穩定的情況下更積極轉換成流動數據連線"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 38ec6c7..2554388 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"藍牙音訊 LDAC 轉碼器:播放品質"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"選取藍牙音訊 LDAC 轉碼器:\n播放品質"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"串流中:<xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"透過傳輸層安全標準 (TLS) 執行 DNS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"如果啟用這個選項,即可在通訊埠 853 上嘗試透過傳輸層安全標準 (TLS) 執行 DNS。"</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"顯示無線螢幕分享認證的選項"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"讓 Wi‑Fi 記錄功能升級,在 Wi‑Fi 選擇器中依每個 SSID RSSI 顯示 Wi‑Fi 詳細紀錄"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"啟用時,Wi-Fi 連線在訊號不穩的情況下會更積極轉換成行動數據連線"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 1c15d54..928eac5 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -209,6 +209,8 @@
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"I-Bluetooth Audio LDAC Codec: Ikhwalithi yokudlala"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Khetha i-Bluetooth Audio LDAC Codec:\nIkhwalithi yokudlala"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Ukusakaza: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+ <string name="dns_tls" msgid="6773814174391131955">"I-DNS nge-TLS"</string>
+ <string name="dns_tls_summary" msgid="3692494150251071380">"Uma inikwe amandla, zama i-DNS nge-TLS embobeni 853."</string>
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"Bonisa izinketho zokunikeza isitifiketi ukubukeka okungenantambo"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"khuphula izinga lokungena le-Wi-Fi, bonisa nge-SSID RSSI engayodwana kusikhethi se-Wi-Fi"</string>
<string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Uma inikwe amandla, i-Wi-Fi izoba namandla kakhulu ekudluliseleni ukuxhumeka kwedatha kuselula, uma isignali ye-Wi-Fi iphansi"</string>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index e261570..bd963e9 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -32,6 +32,7 @@
<dimen name="user_spinner_padding_sides">20dp</dimen>
<dimen name="user_spinner_item_height">56dp</dimen>
+ <dimen name="two_target_pref_small_icon_size">24dp</dimen>
<!-- Lock icon for preferences locked by admin -->
<dimen name="restricted_icon_size">16dp</dimen>
<dimen name="restricted_icon_padding">4dp</dimen>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a404759..360848f 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -235,6 +235,27 @@
<!-- Message for the error dialog when BT pairing fails because the other device rejected the pairing. -->
<string name="bluetooth_pairing_rejected_error_message">Pairing rejected by <xliff:g id="device_name">%1$s</xliff:g>.</string>
+ <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=4875089335641234463] -->
+ <string name="bluetooth_talkback_computer">Computer</string>
+
+ <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=5140152177885220949] -->
+ <string name="bluetooth_talkback_headset">Headset</string>
+
+ <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=4260255181240622896] -->
+ <string name="bluetooth_talkback_phone">Phone</string>
+
+ <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=551146170554589119] -->
+ <string name="bluetooth_talkback_imaging">Imaging</string>
+
+ <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=26580326066627664] -->
+ <string name="bluetooth_talkback_headphone">Headphone</string>
+
+ <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=5165842622743212268] -->
+ <string name="bluetooth_talkback_input_peripheral">Input Peripheral</string>
+
+ <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=5615463912185280812] -->
+ <string name="bluetooth_talkback_bluetooth">Bluetooth</string>
+
<!-- Content description of the WIFI signal when WIFI is disabled for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_wifi_off">Wifi off.</string>
<!-- Content description of the WIFI signal when no signal for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index b7ea1d4..3f312f4 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -21,9 +21,4 @@
<style name="TextAppearanceMedium">
<item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
</style>
-
- <style name="preference_icon_frame">
- <item name="android:layout_marginStart">-4dp</item>
- <item name="android:minWidth">60dp</item>
- </style>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
index 32e6389..c3a36e9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
@@ -343,7 +344,8 @@
}
DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
Context.DEVICE_POLICY_SERVICE);
- if (dpm == null) {
+ PackageManager pm = context.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) || dpm == null) {
return null;
}
boolean isAccountTypeDisabled = false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java b/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java
index 1c161df..8b39f60 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java
@@ -21,41 +21,56 @@
import android.support.v7.preference.PreferenceViewHolder;
import android.util.AttributeSet;
import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
public class TwoTargetPreference extends Preference {
+ private boolean mUseSmallIcon;
+ private int mSmallIconSize;
+
public TwoTargetPreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- init();
+ init(context);
}
public TwoTargetPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- init();
+ init(context);
}
public TwoTargetPreference(Context context, AttributeSet attrs) {
super(context, attrs);
- init();
+ init(context);
}
public TwoTargetPreference(Context context) {
super(context);
- init();
+ init(context);
}
- private void init() {
+ private void init(Context context) {
setLayoutResource(R.layout.preference_two_target);
+ mSmallIconSize = context.getResources().getDimensionPixelSize(
+ R.dimen.two_target_pref_small_icon_size);
final int secondTargetResId = getSecondTargetResId();
if (secondTargetResId != 0) {
setWidgetLayoutResource(secondTargetResId);
}
}
+ public void setUseSmallIcon(boolean useSmallIcon) {
+ mUseSmallIcon = useSmallIcon;
+ }
+
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ if (mUseSmallIcon) {
+ ImageView icon = holder.itemView.findViewById(android.R.id.icon);
+ icon.setLayoutParams(new LinearLayout.LayoutParams(mSmallIconSize, mSmallIconSize));
+ }
final View divider = holder.findViewById(R.id.two_target_divider);
final View widgetFrame = holder.findViewById(android.R.id.widget_frame);
final boolean shouldHideSecondTarget = shouldHideSecondTarget();
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 8def036..d90386f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -12,9 +12,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.Color;
-import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.os.BatteryManager;
@@ -297,4 +295,9 @@
}
return defaultDays;
}
+
+ public static boolean isWifiOnly(Context context) {
+ return !context.getSystemService(ConnectivityManager.class)
+ .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 0946181..764c5922 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -30,6 +30,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.R;
+import com.android.settingslib.wrapper.BluetoothA2dpWrapper;
import java.util.ArrayList;
import java.util.Arrays;
@@ -42,7 +43,6 @@
private Context mContext;
private BluetoothA2dp mService;
- BluetoothA2dpWrapper.Factory mWrapperFactory;
private BluetoothA2dpWrapper mServiceWrapper;
private boolean mIsProfileReady;
@@ -67,7 +67,7 @@
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothA2dp) proxy;
- mServiceWrapper = mWrapperFactory.getInstance(mService);
+ mServiceWrapper = new BluetoothA2dpWrapper(mService);
// We just bound to the service, so refresh the UI for any connected A2DP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
while (!deviceList.isEmpty()) {
@@ -101,14 +101,13 @@
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- mWrapperFactory = new BluetoothA2dpWrapperImpl.Factory();
mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(),
BluetoothProfile.A2DP);
}
@VisibleForTesting
- void setWrapperFactory(BluetoothA2dpWrapper.Factory factory) {
- mWrapperFactory = factory;
+ void setBluetoothA2dpWrapper(BluetoothA2dpWrapper wrapper) {
+ mServiceWrapper = wrapper;
}
public boolean isConnectable() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java
deleted file mode 100644
index aa3e835..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.bluetooth;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothCodecStatus;
-import android.bluetooth.BluetoothDevice;
-
-/**
- * This interface replicates some methods of android.bluetooth.BluetoothA2dp that are new and not
- * yet available in our current version of Robolectric. It provides a thin wrapper to call the real
- * methods in production and a mock in tests.
- */
-public interface BluetoothA2dpWrapper {
-
- static interface Factory {
- BluetoothA2dpWrapper getInstance(BluetoothA2dp service);
- }
-
- /**
- * @return the real {@code BluetoothA2dp} object
- */
- BluetoothA2dp getService();
-
- /**
- * Wraps {@code BluetoothA2dp.getCodecStatus}
- */
- public BluetoothCodecStatus getCodecStatus();
-
- /**
- * Wraps {@code BluetoothA2dp.supportsOptionalCodecs}
- */
- int supportsOptionalCodecs(BluetoothDevice device);
-
- /**
- * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled}
- */
- int getOptionalCodecsEnabled(BluetoothDevice device);
-
- /**
- * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled}
- */
- void setOptionalCodecsEnabled(BluetoothDevice device, int value);
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index d1621da..213002f 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -19,7 +19,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothHidHost;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
@@ -35,7 +35,7 @@
private static final String TAG = "HidProfile";
private static boolean V = true;
- private BluetoothInputDevice mService;
+ private BluetoothHidHost mService;
private boolean mIsProfileReady;
private final LocalBluetoothAdapter mLocalAdapter;
@@ -53,7 +53,7 @@
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (V) Log.d(TAG,"Bluetooth service connected");
- mService = (BluetoothInputDevice) proxy;
+ mService = (BluetoothHidHost) proxy;
// We just bound to the service, so refresh the UI for any connected HID devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
while (!deviceList.isEmpty()) {
@@ -87,7 +87,7 @@
mDeviceManager = deviceManager;
mProfileManager = profileManager;
adapter.getProfileProxy(context, new InputDeviceServiceListener(),
- BluetoothProfile.INPUT_DEVICE);
+ BluetoothProfile.HID_HOST);
}
public boolean isConnectable() {
@@ -190,7 +190,7 @@
if (V) Log.d(TAG, "finalize()");
if (mService != null) {
try {
- BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.INPUT_DEVICE,
+ BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HID_HOST,
mService);
mService = null;
}catch (Throwable t) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 0750dc7..9cda669 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -21,9 +21,9 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHidHost;
import android.bluetooth.BluetoothMap;
import android.bluetooth.BluetoothMapClient;
-import android.bluetooth.BluetoothInputDevice;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothPbapClient;
import android.bluetooth.BluetoothProfile;
@@ -123,7 +123,7 @@
// Always add HID and PAN profiles
mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this);
addProfile(mHidProfile, HidProfile.NAME,
- BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
+ BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
mPanProfile = new PanProfile(context);
addPanProfile(mPanProfile, PanProfile.NAME,
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
index 7bda231..3299cb2 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -32,7 +32,7 @@
/**
* PanProfile handles Bluetooth PAN profile (NAP and PANU).
*/
-public final class PanProfile implements LocalBluetoothProfile {
+public class PanProfile implements LocalBluetoothProfile {
private static final String TAG = "PanProfile";
private static boolean V = true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
index c919426..0ee1dad9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
@@ -1,9 +1,17 @@
package com.android.settingslib.bluetooth;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.util.Pair;
import com.android.settingslib.R;
+import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
+
+import java.util.List;
public class Utils {
public static final boolean V = false; // verbose logging
@@ -40,4 +48,78 @@
void onShowError(Context context, String name, int messageResId);
}
+ public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
+ CachedBluetoothDevice cachedDevice) {
+ return getBtClassDrawableWithDescription(context, cachedDevice, 1 /* iconScale */);
+ }
+
+ public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
+ CachedBluetoothDevice cachedDevice, float iconScale) {
+ BluetoothClass btClass = cachedDevice.getBtClass();
+ final int level = cachedDevice.getBatteryLevel();
+ if (btClass != null) {
+ switch (btClass.getMajorDeviceClass()) {
+ case BluetoothClass.Device.Major.COMPUTER:
+ return new Pair<>(getBluetoothDrawable(context, R.drawable.ic_bt_laptop, level,
+ iconScale),
+ context.getString(R.string.bluetooth_talkback_computer));
+
+ case BluetoothClass.Device.Major.PHONE:
+ return new Pair<>(
+ getBluetoothDrawable(context, R.drawable.ic_bt_cellphone, level,
+ iconScale),
+ context.getString(R.string.bluetooth_talkback_phone));
+
+ case BluetoothClass.Device.Major.PERIPHERAL:
+ return new Pair<>(
+ getBluetoothDrawable(context, HidProfile.getHidClassDrawable(btClass),
+ level, iconScale),
+ context.getString(R.string.bluetooth_talkback_input_peripheral));
+
+ case BluetoothClass.Device.Major.IMAGING:
+ return new Pair<>(
+ getBluetoothDrawable(context, R.drawable.ic_settings_print, level,
+ iconScale),
+ context.getString(R.string.bluetooth_talkback_imaging));
+
+ default:
+ // unrecognized device class; continue
+ }
+ }
+
+ List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
+ for (LocalBluetoothProfile profile : profiles) {
+ int resId = profile.getDrawableResource(btClass);
+ if (resId != 0) {
+ return new Pair<>(getBluetoothDrawable(context, resId, level, iconScale), null);
+ }
+ }
+ if (btClass != null) {
+ if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+ return new Pair<>(
+ getBluetoothDrawable(context, R.drawable.ic_bt_headset_hfp, level,
+ iconScale),
+ context.getString(R.string.bluetooth_talkback_headset));
+ }
+ if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
+ return new Pair<>(
+ getBluetoothDrawable(context, R.drawable.ic_bt_headphones_a2dp, level,
+ iconScale),
+ context.getString(R.string.bluetooth_talkback_headphone));
+ }
+ }
+ return new Pair<>(
+ getBluetoothDrawable(context, R.drawable.ic_settings_bluetooth, level, iconScale),
+ context.getString(R.string.bluetooth_talkback_bluetooth));
+ }
+
+ public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId,
+ int batteryLevel, float iconScale) {
+ if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ return BluetoothDeviceLayerDrawable.createLayerDrawable(context, resId, batteryLevel,
+ iconScale);
+ } else {
+ return context.getDrawable(resId);
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 1771208..1cb255b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -43,6 +43,7 @@
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
+import libcore.util.TimeZoneFinder;
/**
* ZoneGetter is the utility class to get time zone and zone list, and both of them have display
@@ -376,10 +377,9 @@
}
// Create a lookup of local zone IDs.
- localZoneIds = new HashSet<String>();
- for (String olsonId : libcore.icu.TimeZoneNames.forLocale(locale)) {
- localZoneIds.add(olsonId);
- }
+ List<String> zoneIds =
+ TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(locale.getCountry());
+ localZoneIds = new HashSet<>(zoneIds);
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index 6aae226..3c02f6a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -16,11 +16,13 @@
package com.android.settingslib.development;
+import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.UserManager;
import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
import android.support.v14.preference.SwitchPreference;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.preference.Preference;
@@ -95,6 +97,10 @@
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
+ if (isUserAMonkey()) {
+ return false;
+ }
+
if (TextUtils.equals(KEY_ENABLE_ADB, preference.getKey())) {
if (!isAdbEnabled()) {
showConfirmationDialog(preference);
@@ -117,4 +123,9 @@
LocalBroadcastManager.getInstance(mContext)
.sendBroadcast(new Intent(ACTION_ENABLE_ADB_STATE_CHANGED));
}
+
+ @VisibleForTesting
+ boolean isUserAMonkey() {
+ return ActivityManager.isUserAMonkey();
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java
new file mode 100644
index 0000000..a78440c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import android.content.Context;
+import android.os.UserManager;
+
+import com.android.settingslib.Utils;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+public abstract class AbstractSimStatusImeiInfoPreferenceController
+ extends AbstractPreferenceController {
+ public AbstractSimStatusImeiInfoPreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mContext.getSystemService(UserManager.class).isAdminUser()
+ && !Utils.isWifiOnly(mContext);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 35ba6ae..038dcf8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -65,7 +65,7 @@
*
* <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
*/
- private static final String EXTRA_SETTINGS_ACTION =
+ public static final String EXTRA_SETTINGS_ACTION =
"com.android.settings.action.EXTRA_SETTINGS";
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 817989ab..f4c9bb3 100755
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -415,8 +415,8 @@
: (mLevel == 100 ? 0.38f : 0.5f)));
mTextHeight = -mTextPaint.getFontMetrics().ascent;
pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level / 10) : level);
- pctX = mWidth * 0.5f;
- pctY = (mHeight + mTextHeight) * 0.47f;
+ pctX = mWidth * 0.5f + left;
+ pctY = (mHeight + mTextHeight) * 0.47f + top;
pctOpaque = levelTop > pctY;
if (!pctOpaque) {
mTextPath.reset();
@@ -439,8 +439,8 @@
if (!mCharging && !mPowerSaveEnabled) {
if (level <= mCriticalLevel) {
// draw the warning text
- final float x = mWidth * 0.5f;
- final float y = (mHeight + mWarningTextHeight) * 0.48f;
+ final float x = mWidth * 0.5f + left;
+ final float y = (mHeight + mWarningTextHeight) * 0.48f + top;
c.drawText(mWarningString, x, y, mWarningTextPaint);
} else if (pctOpaque) {
// draw the percentage text
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS
new file mode 100644
index 0000000..a0e28ba
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS
@@ -0,0 +1,5 @@
+# Default reviewers for this and subdirectories.
+takaoka@google.com
+yukawa@google.com
+
+# Emergency approvers in case the above are not available
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
similarity index 64%
rename from packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java
rename to packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
index 14fa796..4c52a9f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
@@ -14,48 +14,56 @@
* limitations under the License.
*/
-package com.android.settingslib.bluetooth;
+package com.android.settingslib.wrapper;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
-public class BluetoothA2dpWrapperImpl implements BluetoothA2dpWrapper {
-
- public static class Factory implements BluetoothA2dpWrapper.Factory {
- @Override
- public BluetoothA2dpWrapper getInstance(BluetoothA2dp service) {
- return new BluetoothA2dpWrapperImpl(service);
- }
- }
+/**
+ * This class replicates some methods of android.bluetooth.BluetoothA2dp that are new and not
+ * yet available in our current version of Robolectric. It provides a thin wrapper to call the real
+ * methods in production and a mock in tests.
+ */
+public class BluetoothA2dpWrapper {
private BluetoothA2dp mService;
- public BluetoothA2dpWrapperImpl(BluetoothA2dp service) {
+ public BluetoothA2dpWrapper(BluetoothA2dp service) {
mService = service;
}
- @Override
+ /**
+ * @return the real {@code BluetoothA2dp} object
+ */
public BluetoothA2dp getService() {
return mService;
}
- @Override
+ /**
+ * Wraps {@code BluetoothA2dp.getCodecStatus}
+ */
public BluetoothCodecStatus getCodecStatus() {
return mService.getCodecStatus();
}
- @Override
+ /**
+ * Wraps {@code BluetoothA2dp.supportsOptionalCodecs}
+ */
public int supportsOptionalCodecs(BluetoothDevice device) {
return mService.supportsOptionalCodecs(device);
}
- @Override
+ /**
+ * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled}
+ */
public int getOptionalCodecsEnabled(BluetoothDevice device) {
return mService.getOptionalCodecsEnabled(device);
}
- @Override
+ /**
+ * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled}
+ */
public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
mService.setOptionalCodecsEnabled(device, value);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
index ca366ea..64de635 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
@@ -33,6 +33,7 @@
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.UserManager;
@@ -56,6 +57,8 @@
private DevicePolicyManager mDevicePolicyManager;
@Mock
private UserManager mUserManager;
+ @Mock
+ private PackageManager mPackageManager;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private RestrictedLockUtils.Proxy mProxy;
@@ -72,11 +75,32 @@
.thenReturn(mDevicePolicyManager);
when(mContext.getSystemService(Context.USER_SERVICE))
.thenReturn(mUserManager);
+ when(mContext.getPackageManager())
+ .thenReturn(mPackageManager);
RestrictedLockUtils.sProxy = mProxy;
}
@Test
+ public void checkIfDevicePolicyServiceDisabled_noEnforceAdminForManagedProfile() {
+ when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(null);
+ final EnforcedAdmin enforcedAdmin = RestrictedLockUtils.checkIfAccountManagementDisabled(
+ mContext, "account_type", mUserId);
+
+ assertThat(enforcedAdmin).isEqualTo(null);
+ }
+
+ @Test
+ public void checkIfDeviceAdminFeatureDisabled_noEnforceAdminForManagedProfile() {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN))
+ .thenReturn(false);
+ final EnforcedAdmin enforcedAdmin = RestrictedLockUtils.checkIfAccountManagementDisabled(
+ mContext, "account_type", mUserId);
+
+ assertThat(enforcedAdmin).isEqualTo(null);
+ }
+
+ @Test
public void checkIfKeyguardFeaturesDisabled_noEnforcedAdminForManagedProfile() {
setUpManagedProfile(mUserId, new ComponentName[] {mAdmin1, mAdmin2});
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java
index fa654cb..b5ee5ad 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java
@@ -16,34 +16,31 @@
package com.android.settingslib;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
import android.content.Context;
import android.support.v7.preference.PreferenceViewHolder;
import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
@RunWith(SettingsLibRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class TwoTargetPreferenceTest {
private PreferenceViewHolder mViewHolder;
- @Mock
private View mDivider;
- @Mock
private View mWidgetFrame;
+ private View mRootView;
private TwoTargetPreference mPreference;
private Context mContext;
@@ -52,11 +49,11 @@
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mPreference = spy(new TwoTargetPreference(mContext));
- mViewHolder = PreferenceViewHolder.createInstanceForTests(mock(View.class));
- when(mViewHolder.findViewById(R.id.two_target_divider))
- .thenReturn(mDivider);
- when(mViewHolder.findViewById(android.R.id.widget_frame))
- .thenReturn(mWidgetFrame);
+ mRootView = View.inflate(mContext, R.layout.preference_two_target, null /* parent */);
+ mViewHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
+
+ mDivider = mViewHolder.findViewById(R.id.two_target_divider);
+ mWidgetFrame = mViewHolder.findViewById(android.R.id.widget_frame);
}
@Test
@@ -65,8 +62,8 @@
mPreference.onBindViewHolder(mViewHolder);
- verify(mDivider).setVisibility(View.GONE);
- verify(mWidgetFrame).setVisibility(View.GONE);
+ assertThat(mDivider.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -75,7 +72,37 @@
mPreference.onBindViewHolder(mViewHolder);
- verify(mDivider).setVisibility(View.VISIBLE);
- verify(mWidgetFrame).setVisibility(View.VISIBLE);
+ assertThat(mDivider.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void bind_smallIcon_shouldUseSmallIcon() {
+ mPreference.setUseSmallIcon(true);
+
+ mPreference.onBindViewHolder(mViewHolder);
+
+ final int smallIconSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.two_target_pref_small_icon_size);
+ final LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mViewHolder
+ .findViewById(android.R.id.icon)
+ .getLayoutParams();
+
+ assertThat(layoutParams.width).isEqualTo(smallIconSize);
+ assertThat(layoutParams.height).isEqualTo(smallIconSize);
+ }
+
+ @Test
+ public void bind_normalIcon_shouldUseNormalIcon() {
+ mPreference.setUseSmallIcon(false);
+
+ mPreference.onBindViewHolder(mViewHolder);
+
+ final LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mViewHolder
+ .findViewById(android.R.id.icon)
+ .getLayoutParams();
+
+ assertThat(layoutParams.width).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT);
+ assertThat(layoutParams.height).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index 4a73c1b..ece0d51 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -25,6 +25,7 @@
import com.android.settingslib.R;
import com.android.settingslib.TestConfig;
+import com.android.settingslib.wrapper.BluetoothA2dpWrapper;
import org.junit.Before;
import org.junit.Test;
@@ -73,8 +74,8 @@
}).when(mAdapter).getProfileProxy(any(Context.class), any(), eq(BluetoothProfile.A2DP));
mProfile = new A2dpProfile(mContext, mAdapter, mDeviceManager, mProfileManager);
- mProfile.setWrapperFactory((service) -> { return mBluetoothA2dpWrapper; });
mServiceListener.onServiceConnected(BluetoothProfile.A2DP, mBluetoothA2dp);
+ mProfile.setBluetoothA2dpWrapper(mBluetoothA2dpWrapper);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index b1dbb0a..4091ce1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -59,7 +59,7 @@
@Mock
private A2dpProfile mA2dpProfile;
@Mock
- private HidProfile mHidProfile;
+ private PanProfile mPanProfile;
@Mock
private BluetoothDevice mDevice;
private CachedBluetoothDevice mCachedDevice;
@@ -74,7 +74,7 @@
when(mAdapter.getBluetoothState()).thenReturn(BluetoothAdapter.STATE_ON);
when(mHfpProfile.isProfileReady()).thenReturn(true);
when(mA2dpProfile.isProfileReady()).thenReturn(true);
- when(mHidProfile.isProfileReady()).thenReturn(true);
+ when(mPanProfile.isProfileReady()).thenReturn(true);
mCachedDevice = spy(
new CachedBluetoothDevice(mContext, mAdapter, mProfileManager, mDevice));
doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel();
@@ -92,37 +92,37 @@
@Test
public void testGetConnectionSummary_testSingleProfileConnectDisconnect() {
// Test without battery level
- // Set HID profile to be connected and test connection state summary
- mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+ // Set PAN profile to be connected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
R.string.bluetooth_connected));
- // Set HID profile to be disconnected and test connection state summary
- mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+ // Set PAN profile to be disconnected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
assertThat(mCachedDevice.getConnectionSummary()).isNull();
// Test with battery level
mBatteryLevel = 10;
- // Set HID profile to be connected and test connection state summary
- mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+ // Set PAN profile to be connected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
R.string.bluetooth_connected_battery_level,
com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
- // Set HID profile to be disconnected and test connection state summary
- mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+ // Set PAN profile to be disconnected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
assertThat(mCachedDevice.getConnectionSummary()).isNull();
// Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
- // Set HID profile to be connected and test connection state summary
- mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+ // Set PAN profile to be connected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
R.string.bluetooth_connected));
- // Set HID profile to be disconnected and test connection state summary
- mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+ // Set PAN profile to be disconnected and test connection state summary
+ mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
assertThat(mCachedDevice.getConnectionSummary()).isNull();
}
@@ -130,10 +130,10 @@
public void testGetConnectionSummary_testMultipleProfileConnectDisconnect() {
mBatteryLevel = 10;
- // Set HFP, A2DP and HID profile to be connected and test connection state summary
+ // Set HFP, A2DP and PAN profile to be connected and test connection state summary
mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
R.string.bluetooth_connected_battery_level,
com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
@@ -158,7 +158,7 @@
com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
// Disconnect all profiles and test connection state summary
- mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+ mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
assertThat(mCachedDevice.getConnectionSummary()).isNull();
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/UtilsTest.java
new file mode 100644
index 0000000..5eb543b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/UtilsTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothDevice;
+import android.graphics.drawable.Drawable;
+
+import com.android.settingslib.R;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
+import com.android.settingslib.testutils.shadow.SettingsLibShadowResources;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+ shadows = SettingsLibShadowResources.class)
+public class UtilsTest {
+
+ @Test
+ public void testGetBluetoothDrawable_noBatteryLevel_returnSimpleDrawable() {
+ final Drawable drawable = Utils.getBluetoothDrawable(RuntimeEnvironment.application,
+ R.drawable.ic_bt_laptop, BluetoothDevice.BATTERY_LEVEL_UNKNOWN, 1 /* iconScale */);
+
+ assertThat(drawable).isNotInstanceOf(BluetoothDeviceLayerDrawable.class);
+ }
+
+ @Test
+ public void testGetBluetoothDrawable_hasBatteryLevel_returnLayerDrawable() {
+ final Drawable drawable = Utils.getBluetoothDrawable(RuntimeEnvironment.application,
+ R.drawable.ic_bt_laptop, 10 /* batteryLevel */, 1 /* iconScale */);
+
+ assertThat(drawable).isInstanceOf(BluetoothDeviceLayerDrawable.class);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
index bcabff3..32fa01c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -119,6 +120,18 @@
}
@Test
+ public void handlePreferenceTreeClick_isMonkeyUser_shouldBeFalse() {
+ mController = spy(mController);
+ doReturn(true).when(mController).isUserAMonkey();
+ when(mUserManager.isAdminUser()).thenReturn(true);
+ mController.displayPreference(mScreen);
+
+ final boolean handled = mController.handlePreferenceTreeClick(mPreference);
+
+ assertThat(handled).isFalse();
+ }
+
+ @Test
public void updateState_settingsOn_shouldCheck() {
when(mUserManager.isAdminUser()).thenReturn(true);
Settings.Secure.putInt(mContext.getContentResolver(),
@@ -161,6 +174,7 @@
}
@Override
- public void dismissConfirmationDialog() {}
+ public void dismissConfirmationDialog() {
+ }
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java
new file mode 100644
index 0000000..28409fa
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.shadow.api.Shadow.extract;
+
+import android.net.ConnectivityManager;
+import android.os.UserManager;
+import android.util.SparseBooleanArray;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+ shadows = {SimStatusImeiInfoPreferenceControllerTest.ShadowUserManager.class,
+ SimStatusImeiInfoPreferenceControllerTest.ShadowConnectivityManager.class})
+public class SimStatusImeiInfoPreferenceControllerTest {
+
+ private AbstractSimStatusImeiInfoPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ mController = new AbstractSimStatusImeiInfoPreferenceController(
+ RuntimeEnvironment.application) {
+ @Override
+ public String getPreferenceKey() {
+ return null;
+ }
+ };
+ }
+
+ @Test
+ public void testIsAvailable_isAdminAndHasMobile_shouldReturnTrue() {
+ ShadowUserManager userManager =
+ extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
+ userManager.setIsAdminUser(true);
+ ShadowConnectivityManager connectivityManager =
+ extract(RuntimeEnvironment.application.getSystemService(ConnectivityManager.class));
+ connectivityManager.setNetworkSupported(ConnectivityManager.TYPE_MOBILE, true);
+
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void testIsAvailable_isAdminButNoMobile_shouldReturnFalse() {
+ ShadowUserManager userManager =
+ extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
+ userManager.setIsAdminUser(true);
+ ShadowConnectivityManager connectivityManager =
+ extract(RuntimeEnvironment.application.getSystemService(ConnectivityManager.class));
+ connectivityManager.setNetworkSupported(ConnectivityManager.TYPE_MOBILE, false);
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
+ public void testIsAvailable_isNotAdmin_shouldReturnFalse() {
+ ShadowUserManager userManager =
+ extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
+ userManager.setIsAdminUser(false);
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Implements(UserManager.class)
+ public static class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
+
+ private boolean mAdminUser;
+
+ public void setIsAdminUser(boolean isAdminUser) {
+ mAdminUser = isAdminUser;
+ }
+
+ @Implementation
+ public boolean isAdminUser() {
+ return mAdminUser;
+ }
+ }
+
+ @Implements(ConnectivityManager.class)
+ public static class ShadowConnectivityManager
+ extends org.robolectric.shadows.ShadowConnectivityManager {
+
+ private final SparseBooleanArray mSupportedNetworkTypes = new SparseBooleanArray();
+
+ public void setNetworkSupported(int networkType, boolean supported) {
+ mSupportedNetworkTypes.put(networkType, supported);
+ }
+
+ @Implementation
+ public boolean isNetworkSupported(int networkType) {
+ return mSupportedNetworkTypes.get(networkType);
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
new file mode 100644
index 0000000..3522b8a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.graph;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.testutils.shadow.SettingsLibShadowResources;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+ shadows = SettingsLibShadowResources.class)
+public class BatteryMeterDrawableBaseTest {
+ private static final int PADDING = 5;
+ private static final int HEIGHT = 80;
+ private static final int WIDTH = 40;
+ @Mock
+ private Canvas mCanvas;
+ private Context mContext;
+ private BatteryMeterDrawableBase mBatteryMeterDrawableBase;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mBatteryMeterDrawableBase = new BatteryMeterDrawableBase(mContext, 0 /* frameColor */);
+ }
+
+ @Test
+ public void testDraw_hasPaddingAndBounds_drawWarningInCorrectPosition() {
+ mBatteryMeterDrawableBase.setPadding(PADDING, PADDING, PADDING, PADDING);
+ mBatteryMeterDrawableBase.setBounds(0, 0, WIDTH + 2 * PADDING, HEIGHT + 2 * PADDING);
+ mBatteryMeterDrawableBase.setBatteryLevel(3);
+
+ mBatteryMeterDrawableBase.draw(mCanvas);
+
+ // WIDTH * 0.5 + PADDING = 25
+ // (HEIGHT + TEXT_HEIGHT) * 0.48 + PADDING = 43.3999998
+ verify(mCanvas).drawText(eq("!"), eq(25f), eq(43.399998f), any(Paint.class));
+ }
+
+ @Test
+ public void testDraw_hasPaddingAndBounds_drawBatteryLevelInCorrectPosition() {
+ mBatteryMeterDrawableBase.setPadding(PADDING, PADDING, PADDING, PADDING);
+ mBatteryMeterDrawableBase.setBounds(0, 0, WIDTH + 2 * PADDING, HEIGHT + 2 * PADDING);
+ mBatteryMeterDrawableBase.setBatteryLevel(20);
+ mBatteryMeterDrawableBase.setShowPercent(true);
+
+ mBatteryMeterDrawableBase.draw(mCanvas);
+
+ // WIDTH * 0.5 + PADDING = 25
+ // (HEIGHT + TEXT_HEIGHT) * 0.47 + PADDING = 42.6
+ verify(mCanvas).drawText(eq("20"), eq(25f), eq(42.6f), any(Paint.class));
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index e9d4c37..04ce752 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"اكتب كلمة المرور لإلغاء التأمين"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"اكتب رمز رقم التعريف الشخصي لإلغاء التأمين"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"رمز رقم التعريف الشخصي غير صحيح."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"بطاقة غير صالحة."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"تم الشحن"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"جارٍ الشحن"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"الشحن سريعًا"</string>
diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
index 8af89c3..e24001c 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Unesite lozinku da biste otključali"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Unesite PIN za otključavanje"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN kôd je netačan."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Nevažeća kartica."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Napunjena je"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"Puni se"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Brzo se puni"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index 917effb..22e67b8 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"আনলক করতে পাসওয়ার্ড লিখুন"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"আনলক করতে পিন লিখুন"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"ভুল পিন কোড দেওয়া হয়েছে।"</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"ভুল কার্ড।"</string>
<string name="keyguard_charged" msgid="2222329688813033109">"চার্জ হয়েছে"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"চার্জ হচ্ছে"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"দ্রুত চার্জ হচ্ছে"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index d311ba8..52712287 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Upišite lozinku za otključavanje"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Upišite PIN za otključavanje"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Pogrešan PIN."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Nevažeća kartica."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Napunjeno"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"Punjenje"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Brzo punjenje"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index 9f219ef..9aa8229 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Escriu la contrasenya per desbloquejar"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Escriu el PIN per desbloquejar"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"El codi PIN no és correcte."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"La targeta no és vàlida."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Bateria carregada"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"S\'està carregant"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"S\'està carregant ràpidament"</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
index 57a15fe..dede142 100644
--- a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
@@ -29,6 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Type password to unlock"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Type PIN to unlock"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Incorrect PIN code."</string>
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Invalid Card."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Charged"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"Charging"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Charging rapidly"</string>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index 523eb4c..ac99a84 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Escribe la contraseña para desbloquear"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Escribe el código PIN para desbloquear"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"El código PIN es incorrecto."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Tarjeta no válida."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Cargada"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"Cargando"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Cargando rápidamente"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 08f31da..62ed064 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"अनलॉक करने के लिए पासवर्ड लिखें"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"अनलॉक करने के लिए पिन लिखें"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"गलत पिन कोड."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"गलत कार्ड."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"चार्ज हो गई है"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"चार्ज हो रही है"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"तेज़ी से चार्ज हो रही है"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index 8118d07..7914b5f 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Ապակողպելու համար մուտքագրեք գաղտնաբառը"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Ապակողպելու համար մուտքագրեք PIN կոդը"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN կոդը սխալ է։"</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Սխալ քարտ"</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Լիցքավորված է"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"Լիցքավորում"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Արագ լիցքավորում"</string>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index fbbb4be..e417a3f 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Ketik sandi untuk membuka kunci"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Ketik PIN untuk membuka kunci"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Kode PIN salah."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Kartu Tidak Valid"</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Terisi"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"Mengisi daya"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Mengisi daya dengan cepat"</string>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index 8b4e790..cb61b7d 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"הזן סיסמה לביטול הנעילה"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"הזן את קוד הגישה לביטול הנעילה"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"קוד הגישה שגוי"</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"כרטיס לא חוקי."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"הסוללה טעונה"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"הסוללה נטענת"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"הסוללה נטענת מהר"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index c706900..9b48a4b 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"잠금 해제하려면 비밀번호 입력"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"잠금 해제하려면 PIN 입력"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"잘못된 PIN 코드입니다."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"유효하지 않은 카드"</string>
<string name="keyguard_charged" msgid="2222329688813033109">"충전됨"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"충전 중"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"고속 충전 중"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 32e46f8..d5ac1d8 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"अनलक गर्न पासवर्ड टाइप गर्नुहोस्"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"अनलक गर्न PIN कोड टाइप गर्नुहोस्"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN कोड गलत छ।"</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"अमान्य कार्ड।"</string>
<string name="keyguard_charged" msgid="2222329688813033109">"चार्ज भयो"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"चार्ज हुँदै"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"छिटो चार्ज हुँदै"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 411ee6e..6a1f3c9 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Typ het wachtwoord om te ontgrendelen"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Typ pincode om te ontgrendelen"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Onjuiste pincode."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Ongeldige kaart."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Opgeladen"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"Opladen"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Snel opladen"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index 2c33c98..59f4412 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Wpisz hasło, aby odblokować"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Wpisz kod PIN, aby odblokować"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Nieprawidłowy kod PIN."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Nieprawidłowa karta."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Naładowana"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"Ładowanie"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Szybkie ładowanie"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index bd0bdaa..4f6fcb9 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Introduza a palavra-passe para desbloquear"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Introduza o PIN para desbloquear"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Código PIN incorreto."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Cartão inválido."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Carregada"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"A carregar…"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"A carregar rapidamente…"</string>
@@ -121,7 +120,7 @@
<string name="kg_prompt_reason_switch_profiles_pattern" msgid="3398054847288438444">"É necessário um padrão quando muda de perfil"</string>
<string name="kg_prompt_reason_switch_profiles_pin" msgid="7426368139226961699">"É necessário um PIN quando muda de perfil"</string>
<string name="kg_prompt_reason_switch_profiles_password" msgid="8383831046318421845">"É necessária uma palavra-passe quando muda de perfil"</string>
- <string name="kg_prompt_reason_device_admin" msgid="3452168247888906179">"Dispositivo bloqueado pelo administrador"</string>
+ <string name="kg_prompt_reason_device_admin" msgid="3452168247888906179">"Dispositivo bloqueado pelo gestor"</string>
<string name="kg_prompt_reason_user_request" msgid="8236951765212462286">"O dispositivo foi bloqueado manualmente"</string>
<plurals name="kg_prompt_reason_time_pattern" formatted="false" msgid="71299470072448533">
<item quantity="one">O dispositivo não é desbloqueado há <xliff:g id="NUMBER_0">%d</xliff:g> hora. Confirme o padrão.</item>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index 6c40bd0..9d06e00 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Унесите лозинку да бисте откључали"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Унесите PIN за откључавање"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN кôд је нетачан."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Неважећа картица."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Напуњена је"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"Пуни се"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Брзо се пуни"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 4855161..0ba941c 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"திறக்க, கடவுச்சொல்லை உள்ளிடவும்"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"திறக்க, பின்னை உள்ளிடவும்"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"தவறான பின் குறியீடு."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"செல்லாத சிம் கார்டு."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"சார்ஜ் செய்யப்பட்டது"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"சார்ஜ் ஆகிறது"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"வேகமாகச் சார்ஜாகிறது"</string>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index 5f526c3..b80e4db 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Kilidi açmak için şifreyi yazın"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Kilidi açmak için PIN kodunu yazın"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Yanlış PIN kodu."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Geçersiz Kart."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Ödeme alındı"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"Şarj oluyor"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Hızlı şarj oluyor"</string>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index 88bd095..699ad60 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -29,8 +29,7 @@
<string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Qulfni ochish uchun parolni kiriting"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Qulfni ochish uchun PIN kodni kiriting"</string>
<string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN kodi xato."</string>
- <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
- <skip />
+ <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"SIM karta yaroqsiz."</string>
<string name="keyguard_charged" msgid="2222329688813033109">"Batareya quvvati to‘ldi"</string>
<string name="keyguard_plugged_in" msgid="89308975354638682">"Quvvatlash"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Tezkor quvvat olmoqda"</string>
diff --git a/packages/SystemUI/res/layout/operator_name.xml b/packages/SystemUI/res/layout/operator_name.xml
new file mode 100644
index 0000000..c4f75e9
--- /dev/null
+++ b/packages/SystemUI/res/layout/operator_name.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/operator_name_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ >
+ <com.android.systemui.statusbar.OperatorNameView
+ android:id="@+id/operator_name"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:maxLength="20"
+ android:gravity="center_vertical|start"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:singleLine="true" />
+</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index 35a9477..b138df0 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -63,7 +63,8 @@
android:layout_width="18dp"
android:layout_height="match_parent"
android:src="@drawable/qs_dual_tile_caret"
- android:tint="?android:attr/textColorPrimary" />
+ android:tint="?android:attr/textColorPrimary"
+ android:visibility="gone" />
</LinearLayout>
<TextView
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index c6452c0..6de27ac 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -48,6 +48,11 @@
android:paddingEnd="8dp"
android:orientation="horizontal"
>
+ <ViewStub
+ android:id="@+id/operator_name"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout="@layout/operator_name" />
<!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and
PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). -->
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index bff8d86..b5ac31a 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -169,7 +169,7 @@
<string name="accessibility_battery_level_charging" msgid="1147587904439319646">"جارٍ شحن البطارية، <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> بالمائة."</string>
<string name="accessibility_settings_button" msgid="799583911231893380">"إعدادات النظام."</string>
<string name="accessibility_notifications_button" msgid="4498000369779421892">"الإشعارات."</string>
- <string name="accessibility_overflow_action" msgid="5681882033274783311">"الاطلاع على جميع الإشعارات"</string>
+ <string name="accessibility_overflow_action" msgid="5681882033274783311">"الاطّلاع على جميع الإشعارات"</string>
<string name="accessibility_remove_notification" msgid="3603099514902182350">"محو الإشعار."</string>
<string name="accessibility_gps_enabled" msgid="3511469499240123019">"تم تمكين GPS."</string>
<string name="accessibility_gps_acquiring" msgid="8959333351058967158">"الحصول على GPS."</string>
@@ -477,7 +477,7 @@
<string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> قيد التشغيل"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"سيظل الجهاز مقفلاً إلى أن يتم إلغاء قفله يدويًا"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"الحصول على الإشعارات بشكل أسرع"</string>
- <string name="hidden_notifications_text" msgid="2326409389088668981">"الاطلاع عليها قبل إلغاء القفل"</string>
+ <string name="hidden_notifications_text" msgid="2326409389088668981">"الاطّلاع عليها قبل إلغاء القفل"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"لا، شكرًا"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"إعداد"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index f9a4235..5d621cb 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -701,7 +701,7 @@
<string name="accessibility_action_divider_top_70" msgid="5090779195650364522">"Gore 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="6385859741925078668">"Gore 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="6201455163864841205">"Gore 30%"</string>
- <string name="accessibility_action_divider_bottom_full" msgid="301433196679548001">"Dole cijeli ekran"</string>
+ <string name="accessibility_action_divider_bottom_full" msgid="301433196679548001">"Donji ekran kao cijeli ekran"</string>
<string name="accessibility_qs_edit_tile_label" msgid="8374924053307764245">"Pozicija <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Dvaput dodirnite za uređivanje."</string>
<string name="accessibility_qs_edit_add_tile_label" msgid="8133209638023882667">"<xliff:g id="TILE_NAME">%1$s</xliff:g> Dvaput dodirnite za dodavanje."</string>
<string name="accessibility_qs_edit_position_label" msgid="5055306305919289819">"Pozicija <xliff:g id="POSITION">%1$d</xliff:g>. Dvaput dodirnite za odabir."</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index eea0e63..28b0ffe 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -404,7 +404,7 @@
<string name="battery_saver_notification_title" msgid="237918726750955859">"Estalvi de bateria activat"</string>
<string name="battery_saver_notification_text" msgid="820318788126672692">"Redueix el rendiment i l\'ús de les dades en segon pla."</string>
<string name="battery_saver_notification_action_text" msgid="109158658238110382">"Desactiva l\'estalvi de bateria"</string>
- <string name="media_projection_dialog_text" msgid="3071431025448218928">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> començarà a enregistrar tot el que es mostri a la pantalla."</string>
+ <string name="media_projection_dialog_text" msgid="3071431025448218928">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> començarà a gravar tot el que es mostri a la pantalla."</string>
<string name="media_projection_remember_text" msgid="3103510882172746752">"No ho tornis a mostrar"</string>
<string name="clear_all_notifications_text" msgid="814192889771462828">"Esborra-ho tot"</string>
<string name="media_projection_action_text" msgid="8470872969457985954">"Comença ara"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 39af7ec..e1326de 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -180,7 +180,6 @@
<string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"All recent applications dismissed."</string>
<string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Open <xliff:g id="APP">%s</xliff:g> application info."</string>
<string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Starting <xliff:g id="APP">%s</xliff:g>."</string>
- <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
<string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notification dismissed."</string>
<string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Notification shade."</string>
<string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Quick settings."</string>
@@ -343,8 +342,6 @@
<string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Split screen to the top"</string>
<string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Split screen to the left"</string>
<string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Split screen to the right"</string>
- <string-array name="recents_blacklist_array">
- </string-array>
<string name="expanded_header_battery_charged" msgid="5945855970267657951">"Charged"</string>
<string name="expanded_header_battery_charging" msgid="205623198487189724">"Charging"</string>
<string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> until full"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 296842f..27d5f1b 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -233,8 +233,8 @@
<string name="accessibility_quick_settings_work_mode_on" msgid="7650588553988014341">"Mode kerja aktif."</string>
<string name="accessibility_quick_settings_work_mode_changed_off" msgid="5605534876107300711">"Mode kerja dinonaktifkan."</string>
<string name="accessibility_quick_settings_work_mode_changed_on" msgid="249840330756998612">"Mode kerja diaktifkan."</string>
- <string name="accessibility_quick_settings_data_saver_changed_off" msgid="650231949881093289">"Penghemat Data nonaktif."</string>
- <string name="accessibility_quick_settings_data_saver_changed_on" msgid="4218725402373934151">"Penghemat Data diaktifkan."</string>
+ <string name="accessibility_quick_settings_data_saver_changed_off" msgid="650231949881093289">"Penghemat Kuota Internet nonaktif."</string>
+ <string name="accessibility_quick_settings_data_saver_changed_on" msgid="4218725402373934151">"Penghemat Kuota Internet diaktifkan."</string>
<string name="accessibility_brightness" msgid="8003681285547803095">"Kecerahan tampilan"</string>
<string name="accessibility_ambient_display_charging" msgid="9084521679384069087">"Mengisi daya"</string>
<string name="data_usage_disabled_dialog_3g_title" msgid="5281770593459841889">"Data 2G-3G dijeda"</string>
@@ -636,9 +636,9 @@
<string name="headset" msgid="4534219457597457353">"Headset"</string>
<string name="accessibility_status_bar_headphones" msgid="9156307120060559989">"Headphone terhubung"</string>
<string name="accessibility_status_bar_headset" msgid="8666419213072449202">"Headset terhubung"</string>
- <string name="data_saver" msgid="5037565123367048522">"Penghemat Data"</string>
- <string name="accessibility_data_saver_on" msgid="8454111686783887148">"Penghemat Data aktif"</string>
- <string name="accessibility_data_saver_off" msgid="8841582529453005337">"Penghemat Data nonaktif"</string>
+ <string name="data_saver" msgid="5037565123367048522">"Penghemat Kuota Internet"</string>
+ <string name="accessibility_data_saver_on" msgid="8454111686783887148">"Penghemat Kuota Internet aktif"</string>
+ <string name="accessibility_data_saver_off" msgid="8841582529453005337">"Penghemat Kuota Internet nonaktif"</string>
<string name="switch_bar_on" msgid="1142437840752794229">"Aktif"</string>
<string name="switch_bar_off" msgid="8803270596930432874">"Nonaktif"</string>
<string name="nav_bar" msgid="1993221402773877607">"Bilah navigasi"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 8afef76..b2bf111 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -69,7 +69,7 @@
<string name="compat_mode_off" msgid="4434467572461327898">"स्क्रिन भर्न तन्काउनुहोस्"</string>
<string name="screenshot_saving_ticker" msgid="7403652894056693515">"स्क्रिनसट बचत गर्दै…"</string>
<string name="screenshot_saving_title" msgid="8242282144535555697">"स्क्रिनसट बचत गर्दै…"</string>
- <string name="screenshot_saving_text" msgid="2419718443411738818">"स्क्रिनसट बचत हुँदै छ।"</string>
+ <string name="screenshot_saving_text" msgid="2419718443411738818">"स्क्रिनसट बचत हुँदैछ।"</string>
<string name="screenshot_saved_title" msgid="6461865960961414961">"स्क्रिनसट क्याप्चर गरियो।"</string>
<string name="screenshot_saved_text" msgid="2685605830386712477">"आफ्नो स्क्रिनसट हेर्न ट्याप गर्नुहोस्।"</string>
<string name="screenshot_failed_title" msgid="705781116746922771">"स्क्रिनसट क्याप्चर गर्न सकिएन।"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 962dc49..2bbc470 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -436,12 +436,12 @@
<string name="disable_vpn" msgid="4435534311510272506">"Desativar a VPN"</string>
<string name="disconnect_vpn" msgid="1324915059568548655">"Desligar VPN"</string>
<string name="monitoring_button_view_policies" msgid="100913612638514424">"Ver Políticas"</string>
- <string name="monitoring_description_named_management" msgid="5281789135578986303">"O dispositivo é gerido pela <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>.\n\nO administrador pode monitorizar e gerir definições, acesso empresarial, aplicações, dados associados ao dispositivo e informações de localização do dispositivo.\n\nContacte o administrador para obter mais informações."</string>
- <string name="monitoring_description_management" msgid="4573721970278370790">"O dispositivo é gerido pela sua entidade.\n\nO administrador pode monitorizar e gerir definições, acesso empresarial, aplicações, dados associados ao dispositivo e informações de localização do dispositivo.\n\nContacte o administrador para obter mais informações."</string>
+ <string name="monitoring_description_named_management" msgid="5281789135578986303">"O dispositivo é gerido pela <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>.\n\nO gestor pode monitorizar e gerir definições, acesso empresarial, aplicações, dados associados ao dispositivo e informações de localização do dispositivo.\n\nContacte o gestor para obter mais informações."</string>
+ <string name="monitoring_description_management" msgid="4573721970278370790">"O dispositivo é gerido pela sua entidade.\n\nO gestor pode monitorizar e gerir definições, acesso empresarial, aplicações, dados associados ao dispositivo e informações de localização do dispositivo.\n\nContacte o gestor para obter mais informações."</string>
<string name="monitoring_description_management_ca_certificate" msgid="5202023784131001751">"A sua entidade instalou uma autoridade de certificação neste dispositivo. O tráfego da sua rede segura pode ser monitorizado ou alterado."</string>
<string name="monitoring_description_managed_profile_ca_certificate" msgid="4683248196789897964">"A sua entidade instalou uma autoridade de certificação no seu perfil de trabalho. O tráfego da sua rede segura pode ser monitorizado ou alterado."</string>
<string name="monitoring_description_ca_certificate" msgid="7886985418413598352">"Está instalada uma autoridade de certificação neste dispositivo. O tráfego da sua rede segura pode ser monitorizado ou alterado."</string>
- <string name="monitoring_description_management_network_logging" msgid="7184005419733060736">"O administrador ativou os registos de rede, que monitorizam o tráfego no seu dispositivo."</string>
+ <string name="monitoring_description_management_network_logging" msgid="7184005419733060736">"O gestor ativou os registos de rede, que monitorizam o tráfego no seu dispositivo."</string>
<string name="monitoring_description_named_vpn" msgid="7403457334088909254">"Está ligado à rede <xliff:g id="VPN_APP">%1$s</xliff:g>, que pode monitorizar a sua atividade de rede, incluindo emails, aplicações e Sites."</string>
<string name="monitoring_description_two_named_vpns" msgid="4198511413729213802">"Está ligado às redes <xliff:g id="VPN_APP_0">%1$s</xliff:g> e <xliff:g id="VPN_APP_1">%2$s</xliff:g>, que podem monitorizar a sua atividade de rede, incluindo emails, aplicações e Sites."</string>
<string name="monitoring_description_managed_profile_named_vpn" msgid="1427905889862420559">"O seu perfil de trabalho está ligado à rede <xliff:g id="VPN_APP">%1$s</xliff:g>, que pode monitorizar a sua atividade de rede, incluindo emails, aplicações e Sites."</string>
@@ -456,14 +456,14 @@
<string name="monitoring_description_vpn_settings" msgid="6434859242636063861">"Abrir as definições de VPN"</string>
<string name="monitoring_description_ca_cert_settings_separator" msgid="4987350385906393626">" "</string>
<string name="monitoring_description_ca_cert_settings" msgid="5489969458872997092">"Abrir credenciais fidedignas"</string>
- <string name="monitoring_description_network_logging" msgid="7223505523384076027">"O seu administrador ativou os registos de rede, que monitorizam o tráfego no seu dispositivo.\n\nPara obter mais informações, contacte o administrador."</string>
+ <string name="monitoring_description_network_logging" msgid="7223505523384076027">"O seu gestor ativou os registos de rede, que monitorizam o tráfego no seu dispositivo.\n\nPara obter mais informações, contacte o gestor."</string>
<string name="monitoring_description_vpn" msgid="4445150119515393526">"Concedeu autorização a uma aplicação para configurar uma ligação VPN.\n\nEsta aplicação pode monitorizar a atividade do dispositivo e da rede, incluindo emails, aplicações e Sites."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"O seu perfil de trabalho é gerido por <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nO seu administrador tem a capacidade de monitorizar a sua atividade da rede, incluindo emails, aplicações e Sites.\n\nPara obter mais informações, contacte o administrador.\n\nAlém disso, está ligado a uma VPN, que pode monitorizar a sua atividade da rede."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"O seu perfil de trabalho é gerido por <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nO seu gestor tem a capacidade de monitorizar a sua atividade da rede, incluindo emails, aplicações e Sites.\n\nPara obter mais informações, contacte o gestor.\n\nAlém disso, está ligado a uma VPN, que pode monitorizar a sua atividade da rede."</string>
<string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
<string name="monitoring_description_app" msgid="1828472472674709532">"Está associado à aplicação <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorizar a sua atividade de rede, incluindo emails, aplicações e Sites."</string>
<string name="monitoring_description_app_personal" msgid="484599052118316268">"Está ligado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorizar a atividade da rede pessoal, incluindo emails, aplicações e Sites."</string>
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Está ligado ao <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorizar a atividade da rede pessoal, incluindo emails, aplicações e Sites."</string>
- <string name="monitoring_description_app_work" msgid="4612997849787922906">"O seu perfil de trabalho é gerido pela <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está associado à aplicação <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorizar a atividade da rede de trabalho, incluindo emails, aplicações e Sites.\n\nContacte o administrador para obter mais informações."</string>
+ <string name="monitoring_description_app_work" msgid="4612997849787922906">"O seu perfil de trabalho é gerido pela <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está associado à aplicação <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorizar a atividade da rede de trabalho, incluindo emails, aplicações e Sites.\n\nContacte o gestor para obter mais informações."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"O seu perfil de trabalho é gerido pela <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está associado à aplicação <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que pode monitorizar a atividade da rede de trabalho, incluindo emails, aplicações e Sites.\n\nTambém está associado à aplicação <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que pode monitorizar a atividade da rede pessoal."</string>
<string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Desbloqueado para <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
<string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> em execução"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 7e4de64..8722fb1 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -337,7 +337,7 @@
<string name="recents_launch_error_message" msgid="2969287838120550506">"“<xliff:g id="APP">%s</xliff:g>” ilovasini ishga tushirib bo‘lmadi."</string>
<string name="recents_launch_disabled_message" msgid="1624523193008871793">"Xavfsiz rejimda <xliff:g id="APP">%s</xliff:g> ilovasi o‘chirib qo‘yildi."</string>
<string name="recents_stack_action_button_label" msgid="6593727103310426253">"Hammasini tozalash"</string>
- <string name="recents_drag_hint_message" msgid="2649739267073203985">"Ekranni bo‘lish xususiyatidan foydalanish uchun uchun bu yerga torting"</string>
+ <string name="recents_drag_hint_message" msgid="2649739267073203985">"Ekranni bo‘lish xususiyatidan foydalanish uchun bu yerga torting"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Gorizontal yo‘nalishda bo‘lish"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikal yo‘nalishda bo‘lish"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Boshqa usulda bo‘lish"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 0fe81d9..f54115b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -301,6 +301,9 @@
<!-- Enable the default volume dialog -->
<bool name="enable_volume_ui">true</bool>
+ <!-- Whether to show operator name in the status bar -->
+ <bool name="config_showOperatorNameInStatusBar">false</bool>
+
<!-- Duration of the full carrier network change icon animation. -->
<integer name="carrier_network_change_anim_time">3000</integer>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java
new file mode 100644
index 0000000..a46fab3
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.recents.view;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
+
+import java.util.List;
+import java.util.concurrent.FutureTask;
+
+/**
+ * To be implemented by a particular animation to asynchronously provide the animation specs for a
+ * particular transition.
+ */
+public abstract class AppTransitionAnimationSpecsFuture {
+
+ private final Handler mHandler;
+ private FutureTask<List<AppTransitionAnimationSpec>> mComposeTask = new FutureTask<>(() -> {
+ synchronized (AppTransitionAnimationSpecsFuture.this) {
+ return composeSpecs();
+ }
+ });
+
+ private final IAppTransitionAnimationSpecsFuture mFuture =
+ new IAppTransitionAnimationSpecsFuture.Stub() {
+ @Override
+ public AppTransitionAnimationSpec[] get() throws RemoteException {
+ try {
+ if (!mComposeTask.isDone()) {
+ mHandler.post(mComposeTask);
+ }
+ List<AppTransitionAnimationSpec> specs = mComposeTask.get();
+ if (specs == null) {
+ return null;
+ }
+
+ AppTransitionAnimationSpec[] arr = new AppTransitionAnimationSpec[specs.size()];
+ specs.toArray(arr);
+ return arr;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ };
+
+ public AppTransitionAnimationSpecsFuture(Handler handler) {
+ mHandler = handler;
+ }
+
+ /**
+ * Returns the future to handle the call from window manager.
+ */
+ public final IAppTransitionAnimationSpecsFuture getFuture() {
+ return mFuture;
+ }
+
+ /**
+ * Called ahead of the future callback to compose the specs to be returned in the future.
+ */
+ public final void composeSpecsSynchronous() {
+ if (Looper.myLooper() != mHandler.getLooper()) {
+ throw new RuntimeException("composeSpecsSynchronous() called from wrong looper");
+ }
+ mComposeTask.run();
+ }
+
+ public abstract List<AppTransitionAnimationSpec> composeSpecs();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java
new file mode 100644
index 0000000..3095d15
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.recents.view;
+
+import android.app.ActivityOptions;
+import android.app.ActivityOptions.OnAnimationStartedListener;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.View;
+
+import java.util.function.Consumer;
+
+/**
+ * A helper class to create transitions to/from an App to Recents.
+ */
+public class RecentsTransition {
+
+ /**
+ * Creates a new transition aspect scaled transition activity options.
+ */
+ public static ActivityOptions createAspectScaleAnimation(Context context, Handler handler,
+ boolean scaleUp, AppTransitionAnimationSpecsFuture animationSpecsFuture,
+ OnAnimationStartedListener animationStartCallback) {
+ final OnAnimationStartedListener animStartedListener = new OnAnimationStartedListener() {
+ private boolean mHandled;
+
+ @Override
+ public void onAnimationStarted() {
+ // OnAnimationStartedListener can be called numerous times, so debounce here to
+ // prevent multiple callbacks
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ if (animationStartCallback != null) {
+ animationStartCallback.onAnimationStarted();
+ }
+ }
+ };
+ final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(
+ context, handler,
+ animationSpecsFuture != null ? animationSpecsFuture.getFuture() : null,
+ animStartedListener, scaleUp);
+ return opts;
+ }
+
+ /**
+ * Wraps a animation-start callback in a binder that can be called from window manager.
+ */
+ public static IRemoteCallback wrapStartedListener(Handler handler,
+ OnAnimationStartedListener listener) {
+ if (listener == null) {
+ return null;
+ }
+ return new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ handler.post(listener::onAnimationStarted);
+ }
+ };
+ }
+
+ /**
+ * @return a {@link GraphicBuffer} with the {@param view} drawn into it. Result can be null if
+ * we were unable to allocate a hardware bitmap.
+ */
+ public static GraphicBuffer drawViewIntoGraphicBuffer(int width, int height, View view,
+ float scale, int eraseColor) {
+ final Bitmap hwBitmap = createHardwareBitmap(width, height, (c) -> {
+ c.scale(scale, scale);
+ if (eraseColor != 0) {
+ c.drawColor(eraseColor);
+ }
+ if (view != null) {
+ view.draw(c);
+ }
+ });
+ return hwBitmap != null ? hwBitmap.createGraphicBufferHandle() : null;
+ }
+
+ /**
+ * @return a hardware {@link Bitmap} after being drawn with the {@param consumer}. Result can be
+ * null if we were unable to allocate a hardware bitmap.
+ */
+ public static Bitmap createHardwareBitmap(int width, int height, Consumer<Canvas> consumer) {
+ RenderNode node = RenderNode.create("RecentsTransition", null);
+ node.setLeftTopRightBottom(0, 0, width, height);
+ node.setClipToBounds(false);
+ DisplayListCanvas c = node.start(width, height);
+ consumer.accept(c);
+ node.end(c);
+ return ThreadedRenderer.createHardwareBitmap(node, width, height);
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 3f93f76..090617d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -50,11 +50,13 @@
private final PackageManager mPackageManager;
private final IconDrawableFactory mDrawableFactory;
+ private final BackgroundExecutor mBackgroundExecutor;
private ActivityManagerWrapper() {
final Context context = AppGlobals.getInitialApplication();
mPackageManager = context.getPackageManager();
mDrawableFactory = IconDrawableFactory.newInstance(context);
+ mBackgroundExecutor = BackgroundExecutor.get();
}
public static ActivityManagerWrapper getInstance() {
@@ -198,4 +200,52 @@
}
return label;
}
+
+ /**
+ * Requests that the system close any open system windows (including other SystemUI).
+ */
+ public void closeSystemWindows(String reason) {
+ mBackgroundExecutor.submit(() -> {
+ try {
+ ActivityManager.getService().closeSystemDialogs(reason);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to close system windows", e);
+ }
+ });
+ }
+
+ /**
+ * Removes a task by id.
+ */
+ public void removeTask(int taskId) {
+ mBackgroundExecutor.submit(() -> {
+ try {
+ ActivityManager.getService().removeTask(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to remove task=" + taskId, e);
+ }
+ });
+ }
+
+ /**
+ * Cancels the current window transtion to/from Recents for the given task id.
+ */
+ public void cancelWindowTransition(int taskId) {
+ try {
+ ActivityManager.getService().cancelTaskWindowTransition(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
+ }
+ }
+
+ /**
+ * Cancels the current thumbnail transtion to/from Recents for the given task id.
+ */
+ public void cancelThumbnailTransition(int taskId) {
+ try {
+ ActivityManager.getService().cancelTaskThumbnailTransition(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
+ }
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/BackgroundExecutor.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/BackgroundExecutor.java
new file mode 100644
index 0000000..26e1813
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/BackgroundExecutor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.system;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Offloads work from other threads by running it in a background thread.
+ */
+public class BackgroundExecutor {
+
+ private static final BackgroundExecutor sInstance = new BackgroundExecutor();
+
+ private final ExecutorService mExecutorService = Executors.newFixedThreadPool(2);
+
+ /**
+ * @return the static instance of the background executor.
+ */
+ public static BackgroundExecutor get() {
+ return sInstance;
+ }
+
+ /**
+ * Runs the given {@param runnable} on one of the background executor threads.
+ */
+ public Future<?> submit(Runnable runnable) {
+ return mExecutorService.submit(runnable);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListener.java
rename to packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index 6d0952a..36310f1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -14,24 +14,22 @@
* limitations under the License.
*/
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.system;
import android.app.ActivityManager.TaskSnapshot;
-import android.content.Context;
import android.os.UserHandle;
import android.util.Log;
/**
- * An abstract class to track task stack changes.
- * Classes should implement this instead of {@link android.app.ITaskStackListener}
- * to reduce IPC calls from system services. These callbacks will be called on the main thread.
+ * An interface to track task stack changes. Classes should implement this instead of
+ * {@link android.app.ITaskStackListener} to reduce IPC calls from system services.
*/
public abstract class TaskStackChangeListener {
- /**
- * NOTE: This call is made of the thread that the binder call comes in on.
- */
+ // Binder thread callbacks
public void onTaskStackChangedBackground() { }
+
+ // Main thread callbacks
public void onTaskStackChanged() { }
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
@@ -45,17 +43,16 @@
public void onTaskProfileLocked(int taskId, int userId) { }
/**
- * Checks that the current user matches the user's SystemUI process. Since
+ * Checks that the current user matches the process. Since
* {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
- * TaskStackChangeListener should make this call to verify that we don't act on events from other
- * user's processes.
+ * {@link TaskStackChangeListener} should make this call to verify that we don't act on events
+ * originating from another user's interactions.
*/
- protected final boolean checkCurrentUserId(Context context, boolean debug) {
+ protected final boolean checkCurrentUserId(int currentUserId, boolean debug) {
int processUserId = UserHandle.myUserId();
- int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
if (processUserId != currentUserId) {
if (debug) {
- Log.d(SystemServicesProxy.TAG, "UID mismatch. SystemUI is running uid=" + processUserId
+ Log.d("TaskStackChangeListener", "UID mismatch. Process is uid=" + processUserId
+ " and the current user is uid=" + currentUserId);
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListeners.java
rename to packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index 8eb70f0..95fc96f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.system;
import android.app.ActivityManager.TaskSnapshot;
import android.app.IActivityManager;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 2bb992c..623fe53 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -76,8 +76,8 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
import com.google.android.collect.Lists;
@@ -1738,6 +1738,10 @@
mFailedAttempts.delete(sCurrentUser);
}
+ public ServiceState getServiceState(int subId) {
+ return mServiceStates.get(subId);
+ }
+
public int getFailedUnlockAttempts(int userId) {
return mFailedAttempts.get(userId, 0);
}
@@ -1772,7 +1776,8 @@
}
}
- private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ private final SysUiTaskStackChangeListener
+ mTaskStackListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskStackChangedBackground() {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/DemoMode.java b/packages/SystemUI/src/com/android/systemui/DemoMode.java
index 11996d0..5c39715 100644
--- a/packages/SystemUI/src/com/android/systemui/DemoMode.java
+++ b/packages/SystemUI/src/com/android/systemui/DemoMode.java
@@ -37,4 +37,5 @@
public static final String COMMAND_STATUS = "status";
public static final String COMMAND_NOTIFICATIONS = "notifications";
public static final String COMMAND_VOLUME = "volume";
+ public static final String COMMAND_OPERATOR = "operator";
}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java
index c930d56..3714c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java
@@ -34,6 +34,10 @@
*/
public class ForegroundServiceControllerImpl
implements ForegroundServiceController {
+
+ // shelf life of foreground services before they go bad
+ public static final long FG_SERVICE_GRACE_MILLIS = 5000;
+
private static final String TAG = "FgServiceController";
private static final boolean DBG = false;
@@ -72,7 +76,7 @@
if (isDungeonNotification(sbn)) {
// if you remove the dungeon entirely, we take that to mean there are
// no running services
- userServices.setRunningServices(null);
+ userServices.setRunningServices(null, 0);
return true;
} else {
// this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE
@@ -94,7 +98,7 @@
final Bundle extras = sbn.getNotification().extras;
if (extras != null) {
final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS);
- userServices.setRunningServices(svcs); // null ok
+ userServices.setRunningServices(svcs, sbn.getNotification().when);
}
} else {
userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
@@ -118,9 +122,11 @@
*/
private static class UserServices {
private String[] mRunning = null;
+ private long mServiceStartTime = 0;
private ArrayMap<String, ArraySet<String>> mNotifications = new ArrayMap<>(1);
- public void setRunningServices(String[] pkgs) {
+ public void setRunningServices(String[] pkgs, long serviceStartTime) {
mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
+ mServiceStartTime = serviceStartTime;
}
public void addNotification(String pkg, String key) {
if (mNotifications.get(pkg) == null) {
@@ -142,7 +148,9 @@
return found;
}
public boolean isDungeonNeeded() {
- if (mRunning != null) {
+ if (mRunning != null
+ && System.currentTimeMillis() - mServiceStartTime >= FG_SERVICE_GRACE_MILLIS) {
+
for (String pkg : mRunning) {
final ArraySet<String> set = mNotifications.get(pkg);
if (set == null || set.size() == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
index 44a044b..880ae70 100644
--- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
@@ -28,7 +28,7 @@
/**
* Docks the top-most task and opens recents.
*/
- boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
+ boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds,
int metricsDockAction);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index 4c3d5ba..4119ec0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -26,14 +26,16 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
public class WorkLockActivityController {
+ private static final String TAG = WorkLockActivityController.class.getSimpleName();
+
private final Context mContext;
- private final SystemServicesProxy mSsp;
private final IActivityManager mIam;
public WorkLockActivityController(Context context) {
@@ -43,17 +45,22 @@
@VisibleForTesting
WorkLockActivityController(Context context, SystemServicesProxy ssp, IActivityManager am) {
mContext = context;
- mSsp = ssp;
mIam = am;
- mSsp.registerTaskStackListener(mLockListener);
+ ssp.registerTaskStackListener(mLockListener);
}
private void startWorkChallengeInTask(int taskId, int userId) {
+ ActivityManager.TaskDescription taskDescription = null;
+ try {
+ taskDescription = mIam.getTaskDescription(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get description for task=" + taskId);
+ }
Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
.setComponent(new ComponentName(mContext, WorkLockActivity.class))
.putExtra(Intent.EXTRA_USER_ID, userId)
- .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, mSsp.getTaskDescription(taskId))
+ .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, taskDescription)
.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -67,7 +74,11 @@
} else {
// Starting the activity inside the task failed. We can't be sure why, so to be
// safe just remove the whole task if it still exists.
- mSsp.removeTask(taskId);
+ try {
+ mIam.removeTask(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get description for task=" + taskId);
+ }
}
}
@@ -96,7 +107,7 @@
}
}
- private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() {
+ private final SysUiTaskStackChangeListener mLockListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskProfileLocked(int taskId, int userId) {
startWorkChallengeInTask(taskId, userId);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
index e6d6c55..db4f988 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
@@ -18,6 +18,8 @@
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
@@ -77,7 +79,8 @@
}
}
- private IWindowManager mWindowManager;
+ private final IWindowManager mWindowManager;
+ private final IBinder mToken;
private PipInputEventReceiver mInputEventReceiver;
private TouchListener mListener;
@@ -85,6 +88,7 @@
public InputConsumerController(IWindowManager windowManager) {
mWindowManager = windowManager;
+ mToken = new Binder();
registerInputConsumer();
}
@@ -122,7 +126,7 @@
final InputChannel inputChannel = new InputChannel();
try {
mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
- mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel);
+ mWindowManager.createInputConsumer(mToken, INPUT_CONSUMER_PIP, inputChannel);
} catch (RemoteException e) {
Log.e(TAG, "Failed to create PIP input consumer", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 7e87666..7ef0f15 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -40,8 +40,9 @@
import com.android.systemui.pip.BasePipManager;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.component.ExpandPipEvent;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.io.PrintWriter;
@@ -69,7 +70,7 @@
/**
* Handler for system task stack changes.
*/
- TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() {
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
mTouchHandler.onActivityPinned();
@@ -202,9 +203,9 @@
StackInfo stackInfo = mActivityManager.getStackInfo(
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (stackInfo != null && stackInfo.taskIds != null) {
- SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
+ ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
for (int taskId : stackInfo.taskIds) {
- ssp.cancelThumbnailTransition(taskId);
+ am.cancelThumbnailTransition(taskId);
}
}
} catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 312b990..5a91def 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -45,8 +45,8 @@
import com.android.systemui.R;
import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -599,8 +599,8 @@
private boolean isSettingsShown() {
List<RunningTaskInfo> runningTasks;
try {
- runningTasks = mActivityManager.getTasks(1, 0);
- if (runningTasks == null || runningTasks.size() == 0) {
+ runningTasks = mActivityManager.getTasks(1);
+ if (runningTasks.isEmpty()) {
return false;
}
} catch (RemoteException e) {
@@ -620,7 +620,7 @@
return false;
}
- private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ private SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index 8869e8d..ddd9910 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -185,10 +185,10 @@
}
view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
- if (item.iconDrawable != null) {
- iv.setImageDrawable(item.iconDrawable.getDrawable(iv.getContext()));
+ if (item.icon != null) {
+ iv.setImageDrawable(item.icon.getDrawable(iv.getContext()));
} else {
- iv.setImageResource(item.icon);
+ iv.setImageResource(item.iconResId);
}
iv.getOverlay().clear();
if (item.overlay != null) {
@@ -258,8 +258,8 @@
}
public static class Item {
- public int icon;
- public QSTile.Icon iconDrawable;
+ public int iconResId;
+ public QSTile.Icon icon;
public Drawable overlay;
public CharSequence line1;
public CharSequence line2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 3199decb..532ead1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
+
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
import android.app.ActivityManager;
@@ -37,7 +39,6 @@
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -51,10 +52,11 @@
import com.android.systemui.R;
import com.android.systemui.R.dimen;
import com.android.systemui.R.id;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.qs.TouchAnimator.Listener;
import com.android.systemui.qs.TouchAnimator.ListenerAdapter;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.ExpandableIndicator;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.phone.SettingsButton;
@@ -70,7 +72,7 @@
public class QSFooterImpl extends FrameLayout implements QSFooter,
NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
- SignalCallback {
+ SignalCallback, CommandQueue.Callbacks {
private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
private ActivityStarter mActivityStarter;
@@ -83,6 +85,7 @@
private View mAlarmStatusCollapsed;
private View mDate;
+ private boolean mQsDisabled;
private QSPanel mQsPanel;
private boolean mExpanded;
@@ -278,9 +281,16 @@
}
@Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+ }
+
+ @Override
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
super.onDetachedFromWindow();
}
@@ -302,6 +312,14 @@
return findViewById(R.id.expand_indicator);
}
+ @Override
+ public void disable(int state1, int state2, boolean animate) {
+ final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
+ if (disabled == mQsDisabled) return;
+ mQsDisabled = disabled;
+ updateEverything();
+ }
+
public void updateEverything() {
post(() -> {
updateVisibilities();
@@ -311,8 +329,13 @@
private void updateVisibilities() {
updateAlarmVisibilities();
+
+ mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+
+ mExpandIndicator.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
+
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() && !isDemo
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 00b883a..947b23f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -25,7 +25,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.*;
+import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.plugins.qs.QSTileView;
@@ -43,6 +43,7 @@
public static final String NUM_QUICK_TILES = "sysui_qqs_count";
+ private boolean mDisabledByPolicy;
private int mMaxTiles;
protected QSPanel mFullPanel;
@@ -151,6 +152,30 @@
return Dependency.get(TunerService.class).getValue(NUM_QUICK_TILES, 6);
}
+ void setDisabledByPolicy(boolean disabled) {
+ if (disabled != mDisabledByPolicy) {
+ mDisabledByPolicy = disabled;
+ setVisibility(disabled ? View.GONE : View.VISIBLE);
+ }
+ }
+
+ /**
+ * Sets the visibility of this {@link QuickQSPanel}. This method has no effect when this panel
+ * is disabled by policy through {@link #setDisabledByPolicy(boolean)}, and in this case the
+ * visibility will always be {@link View#GONE}. This method is called externally by
+ * {@link QSAnimator} only.
+ */
+ @Override
+ public void setVisibility(int visibility) {
+ if (mDisabledByPolicy) {
+ if (getVisibility() == View.GONE) {
+ return;
+ }
+ visibility = View.GONE;
+ }
+ super.setVisibility(visibility);
+ }
+
private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 0709e22..398592a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -14,6 +14,9 @@
package com.android.systemui.qs;
+import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -30,13 +33,14 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.R.id;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSDetail.Callback;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
-
-public class QuickStatusBarHeader extends RelativeLayout {
+public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue.Callbacks {
private ActivityStarter mActivityStarter;
@@ -44,6 +48,7 @@
private boolean mExpanded;
private boolean mListening;
+ private boolean mQsDisabled;
protected QuickQSPanel mHeaderQsPanel;
protected QSTileHost mHost;
@@ -119,9 +124,25 @@
}
@Override
+ public void disable(int state1, int state2, boolean animate) {
+ final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
+ if (disabled == mQsDisabled) return;
+ mQsDisabled = disabled;
+ mHeaderQsPanel.setDisabledByPolicy(disabled);
+ final int rawHeight = (int) getResources().getDimension(R.dimen.status_bar_header_height);
+ getLayoutParams().height = disabled ? (rawHeight - mHeaderQsPanel.getHeight()) : rawHeight;
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+ }
+
+ @Override
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
+ SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
super.onDetachedFromWindow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index bc3ccb4..0e4a9fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -134,7 +134,7 @@
if (lastDevice != null) {
int batteryLevel = lastDevice.getBatteryLevel();
if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- state.icon = new BluetoothBatteryDrawable(batteryLevel,
+ state.icon = new BluetoothBatteryTileIcon(lastDevice,
mContext.getResources().getFraction(
R.fraction.bt_battery_scale_fraction, 1, 1));
}
@@ -212,23 +212,20 @@
return new BluetoothDetailAdapter();
}
- private class BluetoothBatteryDrawable extends Icon {
- private int mLevel;
+ private class BluetoothBatteryTileIcon extends Icon {
private float mIconScale;
+ private CachedBluetoothDevice mDevice;
- BluetoothBatteryDrawable(int level) {
- this(level, 1 /* iconScale */);
- }
-
- BluetoothBatteryDrawable(int level, float iconScale) {
- mLevel = level;
+ BluetoothBatteryTileIcon(CachedBluetoothDevice device, float iconScale) {
mIconScale = iconScale;
+ mDevice = device;
}
@Override
public Drawable getDrawable(Context context) {
- return createLayerDrawable(context,
- R.drawable.ic_qs_bluetooth_connected, mLevel, mIconScale);
+ // This method returns Pair<Drawable, String> while first value is the drawable
+ return com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription(
+ context, mDevice, mIconScale).first;
}
}
@@ -302,15 +299,16 @@
for (CachedBluetoothDevice device : devices) {
if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
final Item item = new Item();
- item.icon = R.drawable.ic_qs_bluetooth_on;
+ item.iconResId = R.drawable.ic_qs_bluetooth_on;
item.line1 = device.getName();
item.tag = device;
int state = device.getMaxConnectionState();
if (state == BluetoothProfile.STATE_CONNECTED) {
- item.icon = R.drawable.ic_qs_bluetooth_connected;
+ item.iconResId = R.drawable.ic_qs_bluetooth_connected;
int batteryLevel = device.getBatteryLevel();
if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- item.iconDrawable = new BluetoothBatteryDrawable(batteryLevel);
+ item.icon = new BluetoothBatteryTileIcon(device,
+ 1 /* iconScale */);
item.line2 = mContext.getString(
R.string.quick_settings_connected_battery_level,
Utils.formatPercentage(batteryLevel));
@@ -321,7 +319,7 @@
items.add(connectedDevices, item);
connectedDevices++;
} else if (state == BluetoothProfile.STATE_CONNECTING) {
- item.icon = R.drawable.ic_qs_bluetooth_connecting;
+ item.iconResId = R.drawable.ic_qs_bluetooth_connecting;
item.line2 = mContext.getString(R.string.quick_settings_connecting);
items.add(connectedDevices, item);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index fb396b9..678aa71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -19,26 +19,17 @@
import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
import android.app.Dialog;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.MediaRouter;
-import android.os.UserHandle;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.Button;
-import com.android.internal.app.MediaRouteChooserDialog;
-import com.android.internal.app.MediaRouteControllerDialog;
import com.android.internal.app.MediaRouteDialogPresenter;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -280,7 +271,7 @@
for (CastDevice device : devices) {
if (device.state == CastDevice.STATE_CONNECTED) {
final Item item = new Item();
- item.icon = R.drawable.ic_qs_cast_on;
+ item.iconResId = R.drawable.ic_qs_cast_on;
item.line1 = getDeviceName(device);
item.line2 = mContext.getString(R.string.quick_settings_connected);
item.tag = device;
@@ -300,7 +291,7 @@
final CastDevice device = mVisibleOrder.get(id);
if (!devices.contains(device)) continue;
final Item item = new Item();
- item.icon = R.drawable.ic_qs_cast_off;
+ item.iconResId = R.drawable.ic_qs_cast_off;
item.line1 = getDeviceName(device);
if (device.state == CastDevice.STATE_CONNECTING) {
item.line2 = mContext.getString(R.string.quick_settings_connecting);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 2370273..977a725 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -402,7 +402,7 @@
final AccessPoint ap = mAccessPoints[i];
final Item item = new Item();
item.tag = ap;
- item.icon = mWifiController.getIcon(ap);
+ item.iconResId = mWifiController.getIcon(ap);
item.line1 = ap.getSsid();
item.line2 = ap.isActive() ? ap.getSummary() : null;
item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
index 9214eef..5ae7f22c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -31,7 +31,7 @@
void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
void toggleRecents(int recentsGrowTarget);
void onConfigurationChanged();
- void dockTopTask(int topTaskId, int dragMode, int stackCreateMode,
+ void splitPrimaryTask(int topTaskId, int dragMode, int stackCreateMode,
in Rect initialBounds);
void onDraggingInRecents(float distanceFromTop);
void onDraggingInRecentsEnded(float velocity);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index ce1438a..61c5167 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -63,6 +63,7 @@
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
@@ -246,7 +247,7 @@
return;
}
- sSystemServicesProxy.sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+ ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents();
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
@@ -394,7 +395,7 @@
}
@Override
- public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
+ public boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds,
int metricsDockAction) {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
@@ -426,15 +427,16 @@
runningTask.topActivity.flattenToShortString());
}
if (sSystemServicesProxy.isSystemUser(currentUser)) {
- mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
+ mImpl.splitPrimaryTask(runningTask.id, dragMode, stackCreateMode,
+ initialBounds);
} else {
if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
- callbacks.dockTopTask(runningTask.id, dragMode, stackCreateMode,
- initialBounds);
+ callbacks.splitPrimaryTask(runningTask.id, dragMode,
+ stackCreateMode, initialBounds);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index b75a142..c666d57 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -16,6 +16,9 @@
package com.android.systemui.recents;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
+
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.TaskStackBuilder;
@@ -92,6 +95,7 @@
import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.RecentsView;
import com.android.systemui.recents.views.SystemBarScrimViews;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
@@ -280,8 +284,7 @@
new DismissRecentsToHomeAnimationStarted(animateTaskViews);
dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent,
overrideAnimation));
- Recents.getSystemServices().sendCloseSystemWindows(
- StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+ ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
EventBus.getDefault().send(dismissEvent);
}
@@ -706,9 +709,9 @@
int launchToTaskId = launchState.launchedToTaskId;
if (launchToTaskId != -1 &&
(event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.cancelWindowTransition(launchState.launchedToTaskId);
- ssp.cancelThumbnailTransition(getTaskId());
+ ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
+ am.cancelWindowTransition(launchState.launchedToTaskId);
+ am.cancelThumbnailTransition(getTaskId());
}
}
@@ -755,8 +758,7 @@
loader.deleteTaskData(event.task, false);
// Remove the task from activity manager
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.removeTask(event.task.key.id);
+ ActivityManagerWrapper.getInstance().removeTask(event.task.key.id);
}
public final void onBusEvent(TaskViewDismissedEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 868ed64..96fae35 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -20,6 +20,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.view.View.MeasureSpec;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
+
import android.app.ActivityManager;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
@@ -70,26 +72,28 @@
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ForegroundThread;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.recents.views.RecentsTransitionHelper;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
import com.android.systemui.recents.views.TaskStackView;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.DividerView;
import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
+import java.util.List;
/**
* An implementation of the Recents component for the current user. For secondary users, this can
@@ -113,10 +117,10 @@
public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
/**
- * An implementation of TaskStackChangeListener, that allows us to listen for changes to the system
+ * An implementation of SysUiTaskStackChangeListener, that allows us to listen for changes to the system
* task stacks and update recents accordingly.
*/
- class TaskStackListenerImpl extends TaskStackChangeListener {
+ class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
@Override
public void onTaskStackChangedBackground() {
@@ -445,7 +449,8 @@
growTarget);
// Only close the other system windows if we are actually showing recents
- ssp.sendCloseSystemWindows(StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
+ ActivityManagerWrapper.getInstance().closeSystemWindows(
+ SYSTEM_DIALOG_REASON_RECENT_APPS);
mLastToggleTime = SystemClock.elapsedRealtime();
}
} catch (ActivityNotFoundException e) {
@@ -641,13 +646,13 @@
showRelativeAffiliatedTask(false);
}
- public void dockTopTask(int topTaskId, int dragMode,
- int stackCreateMode, Rect initialBounds) {
+ public void splitPrimaryTask(int taskId, int dragMode, int stackCreateMode,
+ Rect initialBounds) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Make sure we inform DividerView before we actually start the activity so we can change
// the resize mode already.
- if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
+ if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
showRecents(
false /* triggeredFromAltTab */,
@@ -864,22 +869,22 @@
windowOverrideRect);
RectF toTaskRect = toTransform.rect;
- AppTransitionAnimationSpecsFuture future =
- new RecentsTransitionHelper(mContext).getAppTransitionFuture(
- () -> {
- Rect rect = new Rect();
- toTaskRect.round(rect);
- GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
- toTransform);
- return Lists.newArrayList(new AppTransitionAnimationSpec(
- toTask.key.id, thumbnail, rect));
- });
+ AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(mHandler) {
+ @Override
+ public List<AppTransitionAnimationSpec> composeSpecs() {
+ Rect rect = new Rect();
+ toTaskRect.round(rect);
+ GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
+ return Lists.newArrayList(new AppTransitionAnimationSpec(toTask.key.id, thumbnail,
+ rect));
+ }
+ };
// For low end ram devices, wait for transition flag is reset when Recents entrance
// animation is complete instead of when the transition animation starts
- return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
- mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
- false /* scaleUp */), future);
+ return new Pair<>(RecentsTransition.createAspectScaleAnimation(mContext, mHandler,
+ false /* scaleUp */, future, isLowRamDevice ? null : mResetToggleFlagListener),
+ future);
}
/**
@@ -919,7 +924,7 @@
boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
mHeaderBar.onTaskViewSizeChanged(width, height);
if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
- return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
+ return RecentsTransition.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
null, 1f, 0xFFff0000);
} else {
// Workaround for b/27815919, reset the callback so that we do not trigger an
@@ -932,7 +937,7 @@
disabledInSafeMode);
mHeaderBar.onTaskDataLoaded();
mHeaderBar.setDimAlpha(toTransform.dimAlpha);
- return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
+ return RecentsTransition.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
mHeaderBar, 1f, 0);
}
}
@@ -1047,7 +1052,7 @@
Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
EventBus.getDefault().send(new RecentsActivityStartingEvent());
if (future != null) {
- future.precacheSpecs();
+ future.composeSpecsSynchronous();
}
});
EventBus.getDefault().send(hideMenuEvent);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java
index ff9e89e9..9493c78 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java
@@ -90,7 +90,7 @@
}
@Override
- public void dockTopTask(int topTaskId, int dragMode, int stackCreateMode,
+ public void splitPrimaryTask(int topTaskId, int dragMode, int stackCreateMode,
Rect initialBounds) throws RemoteException {
SomeArgs args = SomeArgs.obtain();
args.argi1 = topTaskId;
@@ -144,7 +144,7 @@
break;
case MSG_DOCK_TOP_TASK:
args = (SomeArgs) msg.obj;
- mImpl.dockTopTask(args.argi1, args.argi2, args.argi3 = 0,
+ mImpl.splitPrimaryTask(args.argi1, args.argi2, args.argi3 = 0,
(Rect) args.arg1);
break;
case MSG_ON_DRAGGING_IN_RECENTS:
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
new file mode 100644
index 0000000..5d7f1ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.content.Context;
+
+import com.android.systemui.shared.system.TaskStackChangeListener;
+
+/**
+ * An implementation of {@link TaskStackChangeListener}.
+ */
+public abstract class SysUiTaskStackChangeListener extends TaskStackChangeListener {
+
+ /**
+ * Checks that the current user matches the user's SystemUI process.
+ */
+ protected final boolean checkCurrentUserId(Context context, boolean debug) {
+ int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
+ return checkCurrentUserId(currentUserId, debug);
+ }
+}
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 87f24fd..14b91f7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -79,6 +79,7 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImpl;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.policy.UserInfoController;
import java.util.List;
@@ -200,25 +201,17 @@
*/
public ActivityManager.RunningTaskInfo getRunningTask() {
// Note: The set of running tasks from the system is ordered by recency
- List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(10);
- if (tasks == null || tasks.isEmpty()) {
+ try {
+ List<ActivityManager.RunningTaskInfo> tasks = mIam.getFilteredTasks(1,
+ ACTIVITY_TYPE_RECENTS /* ignoreActivityType */,
+ WINDOWING_MODE_PINNED /* ignoreWindowingMode */);
+ if (tasks.isEmpty()) {
+ return null;
+ }
+ return tasks.get(0);
+ } catch (RemoteException e) {
return null;
}
-
- // Find the first task in a valid stack, we ignore everything from the Recents and PiP
- // stacks
- for (int i = 0; i < tasks.size(); i++) {
- final ActivityManager.RunningTaskInfo task = tasks.get(i);
- final WindowConfiguration winConfig = task.configuration.windowConfiguration;
- if (winConfig.getActivityType() == ACTIVITY_TYPE_RECENTS) {
- continue;
- }
- if (winConfig.getWindowingMode() == WINDOWING_MODE_PINNED) {
- continue;
- }
- return task;
- }
- return null;
}
/**
@@ -299,7 +292,7 @@
try {
final ActivityOptions options = ActivityOptions.makeBasic();
- options.setDockCreateMode(createMode);
+ options.setSplitScreenCreateMode(createMode);
options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
mIam.startActivityFromRecents(taskId, options.toBundle());
return true;
@@ -309,14 +302,15 @@
return false;
}
- /** Docks an already resumed task to the side of the screen. */
- public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) {
+ /** Moves an already resumed task to the side of the screen to initiate split screen. */
+ public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
+ Rect initialBounds) {
if (mIam == null) {
return false;
}
try {
- return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */,
+ return mIam.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */,
false /* animate */, initialBounds);
} catch (RemoteException e) {
e.printStackTrace();
@@ -371,32 +365,6 @@
return insets.right > 0;
}
- /**
- * Cancels the current window transtion to/from Recents for the given task id.
- */
- public void cancelWindowTransition(int taskId) {
- if (mIam == null) return;
-
- try {
- mIam.cancelTaskWindowTransition(taskId);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Cancels the current thumbnail transtion to/from Recents for the given task id.
- */
- public void cancelThumbnailTransition(int taskId) {
- if (mIam == null) return;
-
- try {
- mIam.cancelTaskThumbnailTransition(taskId);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
/** Set the task's windowing mode. */
public void setTaskWindowingMode(int taskId, int windowingMode) {
if (mIam == null) return;
@@ -422,18 +390,6 @@
});
}
- /**
- * Sends a message to close other system windows.
- */
- public void sendCloseSystemWindows(String reason) {
- mUiOffloadThread.submit(() -> {
- try {
- mIam.closeSystemDialogs(reason);
- } catch (RemoteException e) {
- }
- });
- }
-
public ActivityManager.TaskDescription getTaskDescription(int taskId) {
try {
return mIam.getTaskDescription(taskId);
@@ -630,7 +586,7 @@
* Registers a task stack listener with the system.
* This should be called on the main thread.
*/
- public void registerTaskStackListener(TaskStackChangeListener listener) {
+ public void registerTaskStackListener(SysUiTaskStackChangeListener listener) {
if (mIam == null) return;
synchronized (mTaskStackChangeListeners) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java b/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java
index 59f2868..65b96fb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java
@@ -16,8 +16,8 @@
package com.android.systemui.recents.views;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
@@ -71,19 +71,19 @@
public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
null, null, null);
public static final DockState LEFT = new DockState(DOCKED_LEFT,
- DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
new RectF(0, 0, 0.5f, 1));
public static final DockState TOP = new DockState(DOCKED_TOP,
- DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
new RectF(0, 0, 1, 0.5f));
public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
- DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
+ SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
new RectF(0.5f, 0, 1, 1));
public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
- DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+ SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
new RectF(0, 0.5f, 1, 1));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java
new file mode 100644
index 0000000..99390ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionComposer.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 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 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.views;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.content.Context;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.AppTransitionAnimationSpec;
+
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A helper class to create the transition app animation specs to/from Recents
+ */
+public class RecentsTransitionComposer {
+
+ private static final String TAG = "RecentsTransitionComposer";
+
+ private Context mContext;
+ private TaskViewTransform mTmpTransform = new TaskViewTransform();
+
+ public RecentsTransitionComposer(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Composes a single animation spec for the given {@link TaskView}
+ */
+ private static AppTransitionAnimationSpec composeAnimationSpec(TaskStackView stackView,
+ TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
+ GraphicBuffer b = null;
+ if (addHeaderBitmap) {
+ b = composeHeaderBitmap(taskView, transform);
+ if (b == null) {
+ return null;
+ }
+ }
+
+ Rect taskRect = new Rect();
+ transform.rect.round(taskRect);
+ // Disable in for low ram devices because each task does in Recents does not have fullscreen
+ // height (stackView height) and when transitioning to fullscreen app, the code below would
+ // force the task thumbnail to full stackView height immediately causing the transition
+ // jarring.
+ if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
+ stackView.getStack().getStackFrontMostTask()) {
+ taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
+ }
+ return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
+ }
+
+ /**
+ * Composes the transition spec when docking a task, which includes a full task bitmap.
+ */
+ public List<AppTransitionAnimationSpec> composeDockAnimationSpec(TaskView taskView,
+ Rect bounds) {
+ mTmpTransform.fillIn(taskView);
+ Task task = taskView.getTask();
+ GraphicBuffer buffer = RecentsTransitionComposer.composeTaskBitmap(taskView, mTmpTransform);
+ return Collections.singletonList(new AppTransitionAnimationSpec(task.key.id, buffer,
+ bounds));
+ }
+
+ /**
+ * Composes the animation specs for all the tasks in the target stack.
+ */
+ public List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
+ final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
+ // Calculate the offscreen task rect (for tasks that are not backed by views)
+ TaskView taskView = stackView.getChildViewForTask(task);
+ TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
+ Rect offscreenTaskRect = new Rect();
+ stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
+
+ // If this is a full screen stack, the transition will be towards the single, full screen
+ // task. We only need the transition spec for this task.
+
+ // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
+ // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ || activityType == ACTIVITY_TYPE_ASSISTANT
+ || windowingMode == WINDOWING_MODE_UNDEFINED) {
+ List<AppTransitionAnimationSpec> specs = new ArrayList<>();
+ if (taskView == null) {
+ specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
+ } else {
+ mTmpTransform.fillIn(taskView);
+ stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
+ AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, taskView,
+ mTmpTransform, true /* addHeaderBitmap */);
+ if (spec != null) {
+ specs.add(spec);
+ }
+ }
+ return specs;
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Composes a single animation spec for the given {@link Task}
+ */
+ private static AppTransitionAnimationSpec composeOffscreenAnimationSpec(Task task,
+ Rect taskRect) {
+ return new AppTransitionAnimationSpec(task.key.id, null, taskRect);
+ }
+
+ public static GraphicBuffer composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
+ float scale = transform.scale;
+ int fromWidth = (int) (transform.rect.width() * scale);
+ int fromHeight = (int) (transform.rect.height() * scale);
+ if (fromWidth == 0 || fromHeight == 0) {
+ Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
+ " at transform: " + transform);
+
+ return RecentsTransition.drawViewIntoGraphicBuffer(1, 1, null, 1f, 0x00ffffff);
+ } else {
+ if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+ return RecentsTransition.drawViewIntoGraphicBuffer(fromWidth, fromHeight, null, 1f,
+ 0xFFff0000);
+ } else {
+ return RecentsTransition.drawViewIntoGraphicBuffer(fromWidth, fromHeight, taskView,
+ scale, 0);
+ }
+ }
+ }
+
+ private static GraphicBuffer composeHeaderBitmap(TaskView taskView,
+ TaskViewTransform transform) {
+ float scale = transform.scale;
+ int headerWidth = (int) (transform.rect.width());
+ int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
+ if (headerWidth == 0 || headerHeight == 0) {
+ return null;
+ }
+
+ if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+ return RecentsTransition.drawViewIntoGraphicBuffer(headerWidth, headerHeight, null, 1f,
+ 0xFFff0000);
+ } else {
+ return RecentsTransition.drawViewIntoGraphicBuffer(headerWidth, headerHeight,
+ taskView.mHeaderView, scale, 0);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
deleted file mode 100644
index 25c2fc9..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * Copyright (C) 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 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.views;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import android.annotation.Nullable;
-import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IRemoteCallback;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.AppTransitionAnimationSpec;
-import android.view.DisplayListCanvas;
-import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.RenderNode;
-import android.view.ThreadedRenderer;
-import android.view.View;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
-import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
-import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.TaskStack;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A helper class to create transitions to/from Recents
- */
-public class RecentsTransitionHelper {
-
- private static final String TAG = "RecentsTransitionHelper";
- private static final boolean DEBUG = false;
-
- /**
- * Special value for {@link #mAppTransitionAnimationSpecs}: Indicate that we are currently
- * waiting for the specs to be retrieved.
- */
- private static final List<AppTransitionAnimationSpec> SPECS_WAITING = new ArrayList<>();
-
- @GuardedBy("this")
- private List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs = SPECS_WAITING;
-
- private Context mContext;
- private Handler mHandler;
- private TaskViewTransform mTmpTransform = new TaskViewTransform();
-
- private class StartScreenPinningRunnableRunnable implements Runnable {
-
- private int taskId = -1;
-
- @Override
- public void run() {
- EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext, taskId));
- }
- }
- private StartScreenPinningRunnableRunnable mStartScreenPinningRunnable
- = new StartScreenPinningRunnableRunnable();
-
- public RecentsTransitionHelper(Context context) {
- mContext = context;
- mHandler = new Handler();
- }
-
- /**
- * Launches the specified {@link Task}.
- */
- public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
- final TaskStackView stackView, final TaskView taskView,
- final boolean screenPinningRequested, final int windowingMode, final int activityType) {
-
- final ActivityOptions.OnAnimationStartedListener animStartedListener;
- final AppTransitionAnimationSpecsFuture transitionFuture;
- if (taskView != null) {
-
- // Fetch window rect here already in order not to be blocked on lock contention in WM
- // when the future calls it.
- final Rect windowRect = Recents.getSystemServices().getWindowRect();
- transitionFuture = getAppTransitionFuture(() -> composeAnimationSpecs(
- task, stackView, windowingMode, activityType, windowRect));
- animStartedListener = new OnAnimationStartedListener() {
- private boolean mHandled;
-
- @Override
- public void onAnimationStarted() {
- if (mHandled) {
- return;
- }
- mHandled = true;
-
- // If we are launching into another task, cancel the previous task's
- // window transition
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
- EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
- stackView.cancelAllTaskViewAnimations();
-
- if (screenPinningRequested) {
- // Request screen pinning after the animation runs
- mStartScreenPinningRunnable.taskId = task.key.id;
- mHandler.postDelayed(mStartScreenPinningRunnable, 350);
- }
-
- if (!Recents.getConfiguration().isLowRamDevice) {
- // Reset the state where we are waiting for the transition to start
- EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
- }
- }
- };
- } else {
- // This is only the case if the task is not on screen (scrolled offscreen for example)
- transitionFuture = null;
- animStartedListener = new OnAnimationStartedListener() {
- private boolean mHandled;
-
- @Override
- public void onAnimationStarted() {
- if (mHandled) {
- return;
- }
- mHandled = true;
-
- // If we are launching into another task, cancel the previous task's
- // window transition
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
- EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
- stackView.cancelAllTaskViewAnimations();
-
- if (!Recents.getConfiguration().isLowRamDevice) {
- // Reset the state where we are waiting for the transition to start
- EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
- }
- }
- };
- }
-
- EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
- final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
- mHandler, transitionFuture != null ? transitionFuture.future : null,
- animStartedListener, true /* scaleUp */);
- if (taskView == null) {
- // If there is no task view, then we do not need to worry about animating out occluding
- // task views, and we can launch immediately
- startTaskActivity(stack, task, taskView, opts, transitionFuture,
- windowingMode, activityType);
- } else {
- LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
- screenPinningRequested);
- EventBus.getDefault().send(launchStartedEvent);
- startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
- activityType);
- }
- Recents.getSystemServices().sendCloseSystemWindows(
- StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
- }
-
- public IRemoteCallback wrapStartedListener(final OnAnimationStartedListener listener) {
- if (listener == null) {
- return null;
- }
- return new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- listener.onAnimationStarted();
- }
- });
- }
- };
- }
-
- /**
- * Starts the activity for the launch task.
- *
- * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
- * we are toggling recents and the launch-to task is now offscreen.
- */
- private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
- ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
- int windowingMode, int activityType) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.startActivityFromRecents(mContext, task.key, task.title, opts, windowingMode,
- activityType,
- succeeded -> {
- if (succeeded) {
- // Keep track of the index of the task launch
- int taskIndexFromFront = 0;
- int taskIndex = stack.indexOfStackTask(task);
- if (taskIndex > -1) {
- taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
- }
- EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
- } else {
- // Dismiss the task if we fail to launch it
- if (taskView != null) {
- taskView.dismissTask();
- }
-
- // Keep track of failed launches
- EventBus.getDefault().send(new LaunchTaskFailedEvent());
- }
- });
- if (transitionFuture != null) {
- mHandler.post(transitionFuture::precacheSpecs);
- }
- }
-
- /**
- * Creates a future which will later be queried for animation specs for this current transition.
- *
- * @param composer The implementation that composes the specs on the UI thread.
- */
- public AppTransitionAnimationSpecsFuture getAppTransitionFuture(
- final AnimationSpecComposer composer) {
- synchronized (this) {
- mAppTransitionAnimationSpecs = SPECS_WAITING;
- }
- IAppTransitionAnimationSpecsFuture future = new IAppTransitionAnimationSpecsFuture.Stub() {
- @Override
- public AppTransitionAnimationSpec[] get() throws RemoteException {
- mHandler.post(() -> {
- synchronized (RecentsTransitionHelper.this) {
- mAppTransitionAnimationSpecs = composer.composeSpecs();
- RecentsTransitionHelper.this.notifyAll();
- }
- });
- synchronized (RecentsTransitionHelper.this) {
- while (mAppTransitionAnimationSpecs == SPECS_WAITING) {
- try {
- RecentsTransitionHelper.this.wait();
- } catch (InterruptedException e) {}
- }
- if (mAppTransitionAnimationSpecs == null) {
- return null;
- }
- AppTransitionAnimationSpec[] specs
- = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()];
- mAppTransitionAnimationSpecs.toArray(specs);
- mAppTransitionAnimationSpecs = SPECS_WAITING;
- return specs;
- }
- }
- };
- return new AppTransitionAnimationSpecsFuture(composer, future);
- }
-
- /**
- * Composes the transition spec when docking a task, which includes a full task bitmap.
- */
- public List<AppTransitionAnimationSpec> composeDockAnimationSpec(TaskView taskView,
- Rect bounds) {
- mTmpTransform.fillIn(taskView);
- Task task = taskView.getTask();
- GraphicBuffer buffer = RecentsTransitionHelper.composeTaskBitmap(taskView, mTmpTransform);
- return Collections.singletonList(new AppTransitionAnimationSpec(task.key.id, buffer,
- bounds));
- }
-
- /**
- * Composes the animation specs for all the tasks in the target stack.
- */
- private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
- final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
- if (activityType == ACTIVITY_TYPE_RECENTS || activityType == ACTIVITY_TYPE_HOME
- || windowingMode == WINDOWING_MODE_PINNED) {
- return null;
- }
-
- // Calculate the offscreen task rect (for tasks that are not backed by views)
- TaskView taskView = stackView.getChildViewForTask(task);
- TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
- Rect offscreenTaskRect = new Rect();
- stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
-
- // If this is a full screen stack, the transition will be towards the single, full screen
- // task. We only need the transition spec for this task.
-
- // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
- // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
- if (windowingMode == WINDOWING_MODE_FULLSCREEN
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- || activityType == ACTIVITY_TYPE_ASSISTANT
- || windowingMode == WINDOWING_MODE_UNDEFINED) {
- List<AppTransitionAnimationSpec> specs = new ArrayList<>();
- if (taskView == null) {
- specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
- } else {
- mTmpTransform.fillIn(taskView);
- stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
- AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, taskView,
- mTmpTransform, true /* addHeaderBitmap */);
- if (spec != null) {
- specs.add(spec);
- }
- }
- return specs;
- }
- return Collections.emptyList();
- }
-
- /**
- * Composes a single animation spec for the given {@link Task}
- */
- private static AppTransitionAnimationSpec composeOffscreenAnimationSpec(Task task,
- Rect taskRect) {
- return new AppTransitionAnimationSpec(task.key.id, null, taskRect);
- }
-
- public static GraphicBuffer composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
- float scale = transform.scale;
- int fromWidth = (int) (transform.rect.width() * scale);
- int fromHeight = (int) (transform.rect.height() * scale);
- if (fromWidth == 0 || fromHeight == 0) {
- Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
- " at transform: " + transform);
-
- return drawViewIntoGraphicBuffer(1, 1, null, 1f, 0x00ffffff);
- } else {
- if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
- return drawViewIntoGraphicBuffer(fromWidth, fromHeight, null, 1f, 0xFFff0000);
- } else {
- return drawViewIntoGraphicBuffer(fromWidth, fromHeight, taskView, scale, 0);
- }
- }
- }
-
- private static GraphicBuffer composeHeaderBitmap(TaskView taskView,
- TaskViewTransform transform) {
- float scale = transform.scale;
- int headerWidth = (int) (transform.rect.width());
- int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
- if (headerWidth == 0 || headerHeight == 0) {
- return null;
- }
-
- if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
- return drawViewIntoGraphicBuffer(headerWidth, headerHeight, null, 1f, 0xFFff0000);
- } else {
- return drawViewIntoGraphicBuffer(headerWidth, headerHeight, taskView.mHeaderView,
- scale, 0);
- }
- }
-
- public static GraphicBuffer drawViewIntoGraphicBuffer(int bufferWidth, int bufferHeight,
- View view, float scale, int eraseColor) {
- RenderNode node = RenderNode.create("RecentsTransition", null);
- node.setLeftTopRightBottom(0, 0, bufferWidth, bufferHeight);
- node.setClipToBounds(false);
- DisplayListCanvas c = node.start(bufferWidth, bufferHeight);
- c.scale(scale, scale);
- if (eraseColor != 0) {
- c.drawColor(eraseColor);
- }
- if (view != null) {
- view.draw(c);
- }
- node.end(c);
- Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, bufferWidth, bufferHeight);
- return hwBitmap.createGraphicBufferHandle();
- }
-
- /**
- * Composes a single animation spec for the given {@link TaskView}
- */
- private static AppTransitionAnimationSpec composeAnimationSpec(TaskStackView stackView,
- TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
- GraphicBuffer b = null;
- if (addHeaderBitmap) {
- b = composeHeaderBitmap(taskView, transform);
- if (b == null) {
- return null;
- }
- }
-
- Rect taskRect = new Rect();
- transform.rect.round(taskRect);
- // Disable in for low ram devices because each task does in Recents does not have fullscreen
- // height (stackView height) and when transitioning to fullscreen app, the code below would
- // force the task thumbnail to full stackView height immediately causing the transition
- // jarring.
- if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
- stackView.getStack().getStackFrontMostTask()) {
- taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
- }
- return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
- }
-
- public interface AnimationSpecComposer {
- List<AppTransitionAnimationSpec> composeSpecs();
- }
-
- /**
- * Class to be returned from {@link #composeAnimationSpec} that gives access to both the future
- * and the anonymous class used for composing.
- */
- public class AppTransitionAnimationSpecsFuture {
-
- private final AnimationSpecComposer composer;
- private final IAppTransitionAnimationSpecsFuture future;
-
- private AppTransitionAnimationSpecsFuture(AnimationSpecComposer composer,
- IAppTransitionAnimationSpecsFuture future) {
- this.composer = composer;
- this.future = future;
- }
-
- public IAppTransitionAnimationSpecsFuture getFuture() {
- return future;
- }
-
- /**
- * Manually generates and caches the spec such that they are already available when the
- * future needs.
- */
- public void precacheSpecs() {
- synchronized (RecentsTransitionHelper.this) {
- mAppTransitionAnimationSpecs = composer.composeSpecs();
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 5f12a04..f4973d0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -16,8 +16,13 @@
package com.android.systemui.recents.views;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
+
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -28,6 +33,7 @@
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.MathUtils;
@@ -54,15 +60,22 @@
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
import com.android.systemui.recents.events.component.ExpandPipEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
@@ -76,8 +89,9 @@
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.TaskStack;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -101,6 +115,7 @@
private static final int BUSY_RECENTS_TASK_COUNT = 3;
+ private Handler mHandler;
private TaskStackView mTaskStackView;
private TextView mStackActionButton;
private TextView mEmptyView;
@@ -126,7 +141,7 @@
mMultiWindowBackgroundScrim.setAlpha(alpha);
};
- private RecentsTransitionHelper mTransitionHelper;
+ private RecentsTransitionComposer mTransitionHelper;
@ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
private RecentsViewTouchHandler mTouchHandler;
private final FlingAnimationUtils mFlingAnimationUtils;
@@ -148,7 +163,8 @@
setWillNotDraw(false);
SystemServicesProxy ssp = Recents.getSystemServices();
- mTransitionHelper = new RecentsTransitionHelper(getContext());
+ mHandler = new Handler();
+ mTransitionHelper = new RecentsTransitionComposer(getContext());
mDividerSize = ssp.getDockedDividerSize(context);
mTouchHandler = new RecentsViewTouchHandler(this);
mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
@@ -518,9 +534,8 @@
/**** EventBus Events ****/
public final void onBusEvent(LaunchTaskEvent event) {
- mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
- event.taskView, event.screenPinningRequested, event.targetWindowingMode,
- event.targetActivityType);
+ launchTaskFromRecents(getStack(), event.task, mTaskStackView, event.taskView,
+ event.screenPinningRequested, event.targetWindowingMode, event.targetActivityType);
if (Recents.getConfiguration().isLowRamDevice) {
EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
}
@@ -607,17 +622,15 @@
};
final Rect taskRect = getTaskRect(event.taskView);
- AppTransitionAnimationSpecsFuture future =
- mTransitionHelper.getAppTransitionFuture(
- new AnimationSpecComposer() {
- @Override
- public List<AppTransitionAnimationSpec> composeSpecs() {
- return mTransitionHelper.composeDockAnimationSpec(
- event.taskView, taskRect);
- }
- });
+ AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
+ getHandler()) {
+ @Override
+ public List<AppTransitionAnimationSpec> composeSpecs() {
+ return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
+ }
+ };
ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
- mTransitionHelper.wrapStartedListener(startedListener),
+ RecentsTransition.wrapStartedListener(getHandler(), startedListener),
true /* scaleUp */);
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
@@ -881,7 +894,8 @@
* @return the bounds of the stack action button.
*/
Rect getStackActionButtonBoundsFromStackLayout() {
- Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
+ Rect actionButtonRect = new Rect(
+ mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
int left, top;
if (Recents.getConfiguration().isLowRamDevice) {
Rect windowRect = Recents.getSystemServices().getWindowRect();
@@ -906,6 +920,140 @@
return mStackActionButton;
}
+ /**
+ * Launches the specified {@link Task}.
+ */
+ public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
+ final TaskStackView stackView, final TaskView taskView,
+ final boolean screenPinningRequested, final int windowingMode, final int activityType) {
+
+ final ActivityOptions.OnAnimationStartedListener animStartedListener;
+ final AppTransitionAnimationSpecsFuture transitionFuture;
+ if (taskView != null) {
+
+ // Fetch window rect here already in order not to be blocked on lock contention in WM
+ // when the future calls it.
+ final Rect windowRect = Recents.getSystemServices().getWindowRect();
+ transitionFuture = new AppTransitionAnimationSpecsFuture(stackView.getHandler()) {
+ @Override
+ public List<AppTransitionAnimationSpec> composeSpecs() {
+ return mTransitionHelper.composeAnimationSpecs(task, stackView, windowingMode,
+ activityType, windowRect);
+ }
+ };
+ animStartedListener = new OnAnimationStartedListener() {
+ private boolean mHandled;
+
+ @Override
+ public void onAnimationStarted() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ // If we are launching into another task, cancel the previous task's
+ // window transition
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+ EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+ stackView.cancelAllTaskViewAnimations();
+
+ if (screenPinningRequested) {
+ // Request screen pinning after the animation runs
+ mHandler.postDelayed(() -> {
+ EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext,
+ task.key.id));
+ }, 350);
+ }
+
+ if (!Recents.getConfiguration().isLowRamDevice) {
+ // Reset the state where we are waiting for the transition to start
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+ }
+ }
+ };
+ } else {
+ // This is only the case if the task is not on screen (scrolled offscreen for example)
+ transitionFuture = null;
+ animStartedListener = new OnAnimationStartedListener() {
+ private boolean mHandled;
+
+ @Override
+ public void onAnimationStarted() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ // If we are launching into another task, cancel the previous task's
+ // window transition
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+ EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+ stackView.cancelAllTaskViewAnimations();
+
+ if (!Recents.getConfiguration().isLowRamDevice) {
+ // Reset the state where we are waiting for the transition to start
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+ }
+ }
+ };
+ }
+
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
+ final ActivityOptions opts = RecentsTransition.createAspectScaleAnimation(mContext,
+ mHandler, true /* scaleUp */, transitionFuture != null ? transitionFuture : null,
+ animStartedListener);
+ if (taskView == null) {
+ // If there is no task view, then we do not need to worry about animating out occluding
+ // task views, and we can launch immediately
+ startTaskActivity(stack, task, taskView, opts, transitionFuture,
+ windowingMode, activityType);
+ } else {
+ LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
+ screenPinningRequested);
+ EventBus.getDefault().send(launchStartedEvent);
+ startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
+ activityType);
+ }
+ ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+ }
+
+ /**
+ * Starts the activity for the launch task.
+ *
+ * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
+ * we are toggling recents and the launch-to task is now offscreen.
+ */
+ private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
+ ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
+ int windowingMode, int activityType) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.startActivityFromRecents(mContext, task.key, task.title, opts, windowingMode,
+ activityType,
+ succeeded -> {
+ if (succeeded) {
+ // Keep track of the index of the task launch
+ int taskIndexFromFront = 0;
+ int taskIndex = stack.indexOfStackTask(task);
+ if (taskIndex > -1) {
+ taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
+ }
+ EventBus.getDefault().send(new LaunchTaskSucceededEvent(
+ taskIndexFromFront));
+ } else {
+ // Dismiss the task if we fail to launch it
+ if (taskView != null) {
+ taskView.dismissTask();
+ }
+
+ // Keep track of failed launches
+ EventBus.getDefault().send(new LaunchTaskFailedEvent());
+ }
+ });
+ if (transitionFuture != null) {
+ mHandler.post(transitionFuture::composeSpecsSynchronous);
+ }
+ }
+
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 195f4d3..1cda301 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -16,8 +16,8 @@
package com.android.systemui.shortcut;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.os.UserHandle.USER_CURRENT;
import android.app.ActivityManager;
@@ -92,8 +92,8 @@
// If there is no window docked, we dock the top-most window.
Recents recents = getComponent(Recents.class);
int dockMode = (shortcutCode == SC_DOCK_LEFT)
- ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
- : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+ ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+ : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
List<ActivityManager.RecentTaskInfo> taskList =
ActivityManagerWrapper.getInstance().getRecentTasks(1, USER_CURRENT);
recents.showRecentApps(
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 7bcef57..1596d12 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -439,7 +439,7 @@
if (mMinimizedSnapAlgorithm == null) {
mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(),
- mStableInsets, mDockedStackMinimized && mHomeStackResizable);
+ mStableInsets, mDockSide, mDockedStackMinimized && mHomeStackResizable);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index 0997983..b2bbe30 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -31,8 +31,8 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
import com.android.systemui.recents.events.component.ShowUserToastEvent;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
import com.android.systemui.stackdivider.events.StartedDragingEvent;
import com.android.systemui.stackdivider.events.StoppedDragingEvent;
@@ -76,7 +76,7 @@
mContext = context;
EventBus.getDefault().register(this);
SystemServicesProxy.getInstance(context).registerTaskStackListener(
- new TaskStackChangeListener() {
+ new SysUiTaskStackChangeListener() {
@Override
public void onActivityForcedResizable(String packageName, int taskId,
int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 68fe9a8..84b7015 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -29,7 +29,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
-import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -173,12 +172,12 @@
private int mOverrideTint;
private float mOverrideAmount;
private boolean mShadowHidden;
- private boolean mWasActivatedOnDown;
/**
* Similar to mDimmed but is also true if it's not dimmable but should be
*/
private boolean mNeedsDimming;
private int mDimmedAlpha;
+ private boolean mBlockNextTouch;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -204,7 +203,7 @@
} else {
makeInactive(true /* animate */);
}
- }, this::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
+ }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
}
@Override
@@ -241,9 +240,15 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (mNeedsDimming && !mActivated && ev.getActionMasked() == MotionEvent.ACTION_DOWN
+ if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN
&& disallowSingleClick(ev) && !isTouchExplorationEnabled()) {
- return true;
+ if (!mActivated) {
+ return true;
+ } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) {
+ mBlockNextTouch = true;
+ makeInactive(true /* animate */);
+ return true;
+ }
}
return super.onInterceptTouchEvent(ev);
}
@@ -263,10 +268,11 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result;
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- mWasActivatedOnDown = mActivated;
+ if (mBlockNextTouch) {
+ mBlockNextTouch = false;
+ return false;
}
- if ((mNeedsDimming && !mActivated) && !isTouchExplorationEnabled() && isInteractive()) {
+ if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) {
boolean wasActivated = mActivated;
result = handleTouchEventDimmed(event);
if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
@@ -312,7 +318,7 @@
@Override
public boolean performClick() {
- if (mWasActivatedOnDown || !mNeedsDimming || isTouchExplorationEnabled()) {
+ if (!mNeedsDimming || isTouchExplorationEnabled()) {
return super.performClick();
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
new file mode 100644
index 0000000..5090f74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.internal.telephony.IccCardConstants.State;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.WirelessUtils;
+import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.util.List;
+
+public class OperatorNameView extends TextView implements DemoMode, DarkReceiver,
+ SignalCallback, Tunable {
+
+ private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
+
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private boolean mDemoMode;
+
+ private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onRefreshCarrierInfo() {
+ updateText();
+ }
+ };
+
+ public OperatorNameView(Context context) {
+ this(context, null);
+ }
+
+ public OperatorNameView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public OperatorNameView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mKeyguardUpdateMonitor.registerCallback(mCallback);
+ Dependency.get(DarkIconDispatcher.class).addDarkReceiver(this);
+ Dependency.get(NetworkController.class).addCallback(this);
+ Dependency.get(TunerService.class).addTunable(this, KEY_SHOW_OPERATOR_NAME);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mKeyguardUpdateMonitor.removeCallback(mCallback);
+ Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(this);
+ Dependency.get(NetworkController.class).removeCallback(this);
+ Dependency.get(TunerService.class).removeTunable(this);
+ }
+
+ @Override
+ public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+ setTextColor(DarkIconDispatcher.getTint(area, this, tint));
+ }
+
+ @Override
+ public void setIsAirplaneMode(IconState icon) {
+ update();
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ update();
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ if (!mDemoMode && command.equals(COMMAND_ENTER)) {
+ mDemoMode = true;
+ } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
+ mDemoMode = false;
+ update();
+ } else if (mDemoMode && command.equals(COMMAND_OPERATOR)) {
+ setText(args.getString("name"));
+ }
+ }
+
+ private void update() {
+ boolean showOperatorName = Dependency.get(TunerService.class)
+ .getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0;
+ setVisibility(showOperatorName ? VISIBLE : GONE);
+
+ boolean hasMobile = ConnectivityManager.from(mContext)
+ .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
+ if (!hasMobile || airplaneMode) {
+ setText(null);
+ setVisibility(GONE);
+ return;
+ }
+
+ if (!mDemoMode) {
+ updateText();
+ }
+ }
+
+ private void updateText() {
+ CharSequence displayText = null;
+ List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+ final int N = subs.size();
+ for (int i = 0; i < N; i++) {
+ int subId = subs.get(i).getSubscriptionId();
+ State simState = mKeyguardUpdateMonitor.getSimState(subId);
+ CharSequence carrierName = subs.get(i).getCarrierName();
+ if (!TextUtils.isEmpty(carrierName) && simState == State.READY) {
+ ServiceState ss = mKeyguardUpdateMonitor.getServiceState(subId);
+ if (ss != null && ss.getState() == ServiceState.STATE_IN_SERVICE) {
+ displayText = carrierName;
+ break;
+ }
+ }
+ }
+
+ setText(displayText);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index fed2ebe..7022c47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -45,8 +45,8 @@
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
@@ -305,10 +305,10 @@
}
/**
- * An implementation of TaskStackChangeListener, that listens for changes in the system task
+ * An implementation of SysUiTaskStackChangeListener, that listens for changes in the system task
* stack and notifies the navigation bar.
*/
- private class TaskStackListenerImpl extends TaskStackChangeListener {
+ private class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
@Override
public void onTaskStackChanged() {
SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 2c3f452..61f3130 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -60,6 +60,7 @@
private StatusBar mStatusBarComponent;
private DarkIconManager mDarkIconManager;
private SignalClusterView mSignalClusterView;
+ private View mOperatorNameFrame;
private SignalCallback mSignalCallback = new SignalCallback() {
@Override
@@ -97,6 +98,7 @@
// Default to showing until we know otherwise.
showSystemIconArea(false);
initEmergencyCryptkeeperText();
+ initOperatorName();
}
@Override
@@ -150,8 +152,10 @@
if ((diff1 & DISABLE_SYSTEM_INFO) != 0) {
if ((state1 & DISABLE_SYSTEM_INFO) != 0) {
hideSystemIconArea(animate);
+ hideOperatorName(animate);
} else {
showSystemIconArea(animate);
+ showOperatorName(animate);
}
}
if ((diff1 & DISABLE_NOTIFICATION_ICONS) != 0) {
@@ -207,6 +211,18 @@
animateShow(mNotificationIconAreaInner, animate);
}
+ public void hideOperatorName(boolean animate) {
+ if (mOperatorNameFrame != null) {
+ animateHide(mOperatorNameFrame, animate);
+ }
+ }
+
+ public void showOperatorName(boolean animate) {
+ if (mOperatorNameFrame != null) {
+ animateShow(mOperatorNameFrame, animate);
+ }
+ }
+
/**
* Hides a view.
*/
@@ -268,4 +284,11 @@
parent.removeView(emergencyViewStub);
}
}
+
+ private void initOperatorName() {
+ if (getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar)) {
+ ViewStub stub = mStatusBar.findViewById(R.id.operator_name);
+ mOperatorNameFrame = stub.inflate();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
index dcb6a38..0d62703 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
@@ -142,7 +142,7 @@
&& Math.abs(event.getY() - mDownY) < mTouchSlop;
}
- private boolean isWithinDoubleTapSlop(MotionEvent event) {
+ public boolean isWithinDoubleTapSlop(MotionEvent event) {
if (!mActivated) {
// If we're not activated there's no double tap slop to satisfy.
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index ee9a791..8e0a506 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -26,7 +26,6 @@
import android.view.View;
import android.view.ViewConfiguration;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.systemui.Dependency;
@@ -206,7 +205,7 @@
&& mDivider.getView().getWindowManagerProxy().getDockSide() == DOCKED_INVALID) {
Rect initialBounds = null;
int dragMode = calculateDragMode();
- int createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ int createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
if (dragMode == DRAG_MODE_DIVIDER) {
initialBounds = new Rect();
mDivider.getView().calculateBoundsForPosition(mIsVertical
@@ -218,10 +217,10 @@
initialBounds);
} else if (dragMode == DRAG_MODE_RECENTS && mTouchDownX
< mContext.getResources().getDisplayMetrics().widthPixels / 2) {
- createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+ createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
}
- boolean docked = mRecentsComponent.dockTopTask(dragMode, createMode, initialBounds,
- MetricsEvent.ACTION_WINDOW_DOCK_SWIPE);
+ boolean docked = mRecentsComponent.splitPrimaryTask(dragMode, createMode,
+ initialBounds, MetricsEvent.ACTION_WINDOW_DOCK_SWIPE);
if (docked) {
mDragMode = dragMode;
if (mDragMode == DRAG_MODE_DIVIDER) {
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 9a7039a..094129c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -448,10 +448,18 @@
// Always disable recents when alternate car mode UI is active.
boolean disableRecent = mUseCarModeUi
- || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
- final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
+ || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
+
+ boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
&& ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
+ if ((disableRecent || disableBack) && inScreenPinning()) {
+ // Don't hide back and recents buttons when in screen pinning mode, as they are used for
+ // exiting.
+ disableBack = false;
+ disableRecent = false;
+ }
+
ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
if (navButtons != null) {
LayoutTransition lt = navButtons.getLayoutTransition();
@@ -461,20 +469,16 @@
}
}
}
- if (inLockTask() && disableRecent && !disableHome) {
- // Don't hide recents when in lock task, it is used for exiting.
- // Unless home is hidden, then in DPM locked mode and no exit available.
- disableRecent = false;
- }
getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
}
- private boolean inLockTask() {
+ private boolean inScreenPinning() {
try {
- return ActivityManager.getService().isInLockTaskMode();
+ return ActivityManager.getService().getLockTaskModeState()
+ == ActivityManager.LOCK_TASK_MODE_PINNED;
} catch (RemoteException e) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index b876286b..ba4ff58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -65,8 +65,8 @@
import com.android.systemui.UiOffloadThread;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.policy.BluetoothController;
@@ -768,7 +768,7 @@
mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
}
- private final TaskStackChangeListener mTaskListener = new TaskStackChangeListener() {
+ private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
// Listen for changes to stacks and then check which instant apps are foreground.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 9f03954..fa34d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
@@ -1538,8 +1539,8 @@
}
int dockSide = WindowManagerProxy.getInstance().getDockSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
- return mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
- ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
+ return mRecents.splitPrimaryTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
+ ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
} else {
Divider divider = getComponent(Divider.class);
if (divider != null && divider.isMinimized() && !divider.isHomeStackResizable()) {
@@ -4128,6 +4129,9 @@
}
}
}
+ if (modeChange || command.equals(COMMAND_OPERATOR)) {
+ dispatchDemoCommandToView(command, args, R.id.operator_name);
+ }
}
private void dispatchDemoCommandToView(String command, Bundle args, int id) {
@@ -4515,12 +4519,14 @@
final boolean useDarkTheme = systemColors != null
&& (systemColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
if (isUsingDarkTheme() != useDarkTheme) {
- try {
- mOverlayManager.setEnabled("com.android.systemui.theme.dark",
- useDarkTheme, mCurrentUserId);
- } catch (RemoteException e) {
- Log.w(TAG, "Can't change theme", e);
- }
+ mUiOffloadThread.submit(() -> {
+ try {
+ mOverlayManager.setEnabled("com.android.systemui.theme.dark",
+ useDarkTheme, mCurrentUserId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't change theme", e);
+ }
+ });
}
// Lock wallpaper defines the color of the majority of the views, hence we'll use it
@@ -5260,7 +5266,8 @@
boolean isCameraAllowedByAdmin() {
if (mDevicePolicyManager.getCameraDisabled(null, mCurrentUserId)) {
return false;
- } else if (isKeyguardShowing() && isKeyguardSecure()) {
+ } else if (mStatusBarKeyguardViewManager == null ||
+ (isKeyguardShowing() && isKeyguardSecure())) {
// Check if the admin has disabled the camera specifically for the keyguard
return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUserId)
& DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0;
@@ -7091,7 +7098,9 @@
}
public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
- return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
+ return mShowLockscreenNotifications
+ && ((mDisabled2 & DISABLE2_NOTIFICATION_SHADE) == 0)
+ && !mNotificationData.isAmbient(sbn.getKey());
}
// extended in StatusBar
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index d24e51c..40ee838 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -382,13 +382,6 @@
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... args) {
- // Disable tethering if enabling Wifi
- final int wifiApState = mWifiManager.getWifiApState();
- if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
- (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
- mWifiManager.setWifiApEnabled(null, false);
- }
-
mWifiManager.setWifiEnabled(enabled);
return null;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 1f5255a..943020c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -287,6 +287,7 @@
final Bundle extras = new Bundle();
if (pkgs != null) extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS, pkgs);
n.extras = extras;
+ n.when = System.currentTimeMillis() - 10000; // ten seconds ago
final StatusBarNotification sbn = makeMockSBN(userid, "android",
SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
null, n);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index b86fc21..d758314 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -20,7 +20,6 @@
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.argThat;
@@ -41,10 +40,8 @@
import android.support.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.WorkLockActivity;
-import com.android.systemui.keyguard.WorkLockActivityController;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
import org.junit.Before;
import org.junit.Test;
@@ -68,7 +65,7 @@
private @Mock IActivityManager mIActivityManager;
private WorkLockActivityController mController;
- private TaskStackChangeListener mTaskStackListener;
+ private SysUiTaskStackChangeListener mTaskStackListener;
@Before
public void setUp() throws Exception {
@@ -78,8 +75,8 @@
doReturn("com.example.test").when(mContext).getPackageName();
// Construct controller. Save the TaskStackListener for injecting events.
- final ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
- ArgumentCaptor.forClass(TaskStackChangeListener.class);
+ final ArgumentCaptor<SysUiTaskStackChangeListener> listenerCaptor =
+ ArgumentCaptor.forClass(SysUiTaskStackChangeListener.class);
mController =
new WorkLockActivityController(mContext, mSystemServicesProxy, mIActivityManager);
@@ -97,7 +94,7 @@
// The overlay should start and the task the activity started in should not be removed.
verifyStartActivity(TASK_ID, true /*taskOverlay*/);
- verify(mSystemServicesProxy, never()).removeTask(anyInt() /*taskId*/);
+ verify(mIActivityManager, never()).removeTask(anyInt() /*taskId*/);
}
@Test
@@ -111,7 +108,7 @@
// The task the activity started in should be removed to prevent the locked task from
// being shown.
verifyStartActivity(TASK_ID, true /*taskOverlay*/);
- verify(mSystemServicesProxy).removeTask(TASK_ID);
+ verify(mIActivityManager).removeTask(TASK_ID);
}
// End of tests, start of helpers
diff --git a/proto/Android.bp b/proto/Android.bp
new file mode 100644
index 0000000..95f453c
--- /dev/null
+++ b/proto/Android.bp
@@ -0,0 +1,17 @@
+java_library_static {
+ name: "framework-protos",
+ host_supported: true,
+ proto: {
+ type: "nano",
+ },
+ srcs: ["src/**/*.proto"],
+ no_framework_libs: true,
+ target: {
+ android: {
+ jarjar_rules: "jarjar-rules.txt",
+ },
+ host: {
+ static_libs: ["libprotobuf-java-nano"],
+ },
+ },
+}
diff --git a/proto/Android.mk b/proto/Android.mk
deleted file mode 100644
index 1c03d16..0000000
--- a/proto/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := framework-protos
-
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_SRC_FILES:= $(call all-proto-files-under, src)
-LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
-
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-oj core-libart
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk $(LOCAL_PATH)/jarjar-rules.txt
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-# Host-side version of framework-protos
-# ============================================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := host-framework-protos
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_SRC_FILES:= $(call all-proto-files-under, src)
-
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_STATIC_JAVA_LIBRARIES := host-libprotobuf-java-nano
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk $(LOCAL_PATH)/jarjar-rules.txt
-
-include $(BUILD_HOST_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 8e88359..fad6bd1 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4480,7 +4480,7 @@
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
AUTOFILL_INVALID_AUTHENTICATION = 1128;
- // An autofill service used a custom description (using RemoteViews) in the Save affordance
+ // An autofill service used a custom description (using RemoteViews) in the autofill save UI
// Package: Package of app that is autofilled
// OS: O MR
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
@@ -4491,14 +4491,14 @@
// OS: O MR
FIELD_AUTOFILL_SAVE_TYPE = 1130;
- // An autofill service used a custom subtitle (String) in the Save affordance
+ // An autofill service used a custom subtitle (String) in the autofill save UI
// Package: Package of app that is autofilled
// OS: O MR
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
AUTOFILL_SAVE_CUSTOM_SUBTITLE = 1131;
- // User tapped a link in the custom description of the Save affordance provided by an autofill service
+ // User tapped a link in the custom description of the autofill save UI provided by an autofill service
// Package: Package of app that is autofilled
// OS: O MR
// Type TYPE_UNKNOWN: The link was not properly set by the service
@@ -4518,12 +4518,12 @@
// Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
AUTOFILL_SAVE_VALIDATION = 1133;
- // Result of an operation in the autofill save affordance after the user tapped a link in the custom description
+ // Result of an operation in the autofill save UI after the user tapped a link in the custom description
// provided by the autofill service
// Package: Package of app that is autofilled
// OS: O MR
- // Type TYPE_OPEN: The save affordance was restored
- // Type TYPE_DISMISS: The save affordcance was destroyed
+ // Type TYPE_OPEN: The autofill save UI was restored
+ // Type TYPE_DISMISS: The autofill save UI was destroyed
// Type TYPE_FAILURE: An invalid opperation was reported by the app's AutofillManager
AUTOFILL_PENDING_SAVE_UI_OPERATION = 1134;
@@ -4562,6 +4562,11 @@
// OS: O MR
NOTIFICATION_SNOOZE_OPTIONS = 1142;
+ // OPEN: Settings > Display > Colors
+ // CATEGORY: SETTINGS
+ // OS: O MR
+ COLOR_MODE_SETTINGS = 1143;
+
// ---- End O-MR1 Constants, all O-MR1 constants go above this line ----
// OPEN: Settings > Network & Internet > Mobile network
@@ -4699,6 +4704,66 @@
// OS: P
AUTOFILL_SAVE_EXPLICITLY_TRIGGERED = 1229;
+ // OPEN: Settings > Network & Internet > Mobile network > Wi-Fi calling
+ // CATEGORY: SETTINGS
+ // OS: P
+ WIFI_CALLING_FOR_SUB = 1230;
+
+ // An autofill service asked to disable autofill for a given application.
+ // Package: Package of app that is being disabled for autofill
+ // Counter: duration (in ms) that autofill will be disabled
+ // OS: P
+ // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+ // Tag FIELD_CLASS_NAME: Name of the Package of service that processed the request
+ AUTOFILL_SERVICE_DISABLED_APP = 1231;
+
+ // An autofill service asked to disable autofill for a given activity.
+ // Package: Package of app whose activity is being disabled for autofill
+ // Counter: duration (in ms) that autofill will be disabled
+ // OS: P
+ // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+ // Tag FIELD_CLASS_NAME: Class name of the activity that is being disabled for autofill
+ AUTOFILL_SERVICE_DISABLED_ACTIVITY = 1232;
+
+ // ACTION: Stop an app and turn on background check
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_APP_STOP_AND_BACKGROUND_CHECK = 1233;
+
+ // FIELD: The action type for each anomaly
+ // CATEGORY: SETTINGS
+ // OS: P
+ FIELD_ANOMALY_ACTION_TYPE = 1234;
+
+ // OPEN: Settings -> Battery -> Wakelock anomaly
+ // CATEGORY: SETTINGS
+ // OS: P
+ ANOMALY_TYPE_WAKELOCK = 1235;
+
+ // OPEN: Settings -> Battery -> Wakeup alarm anomaly
+ // CATEGORY: SETTINGS
+ // OS: P
+ ANOMALY_TYPE_WAKEUP_ALARM = 1236;
+
+ // OPEN: Settings -> Battery -> Unoptimized bt anomaly
+ // CATEGORY: SETTINGS
+ // OS: P
+ ANOMALY_TYPE_UNOPTIMIZED_BT = 1237;
+
+ // Open: Settings > Dev options > Oem unlock > lock it > warning dialog.
+ // OS: P
+ DIALOG_OEM_LOCK_INFO = 1238;
+
+ // ACTION: Settings > Wi-Fi > Click one network > Auto sign in
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_WIFI_AUTO_SIGN_IN = 1239;
+
+ // Open: Settings > System > About phone > IMEI
+ // CATEGORY: SETTINGS
+ // OS: P
+ DIALOG_IMEI_INFO = 1240;
+
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
}
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index ee0c043..9d25055 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -673,6 +673,10 @@
// Framework initiated disconnect. Sometimes generated to give an extra reason for a disconnect
// Should typically be followed by a NETWORK_DISCONNECTION_EVENT with a local_gen = true
TYPE_FRAMEWORK_DISCONNECT = 15;
+
+ // The NetworkAgent score for wifi has changed in a way that may impact
+ // connectivity
+ TYPE_SCORE_BREACH = 16;
}
enum FrameworkDisconnectReason {
@@ -784,6 +788,9 @@
// Authentication failure reason, as reported by WifiManager (calculated from state & deauth code)
optional AuthFailureReason auth_failure_reason = 13 [default = AUTH_FAILURE_UNKNOWN];
+
+ // NetworkAgent score of connected wifi
+ optional int32 last_score = 14 [default = -1];
}
// Wi-Fi Aware metrics
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 76e7782..6c15438 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -107,8 +107,6 @@
import com.android.server.WidgetBackupProvider;
import com.android.server.policy.IconUtilities;
-import libcore.io.IoUtils;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -174,21 +172,27 @@
Slog.i(TAG, "Received broadcast: " + action + " on user " + userId);
}
- if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
- onConfigurationChanged();
- } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
- || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
- synchronized (mLock) {
- reloadWidgetsMaskedState(userId);
- }
- } else if (Intent.ACTION_PACKAGES_SUSPENDED.equals(action)) {
- String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- updateWidgetPackageSuspensionMaskedState(packages, true, getSendingUserId());
- } else if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(action)) {
- String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- updateWidgetPackageSuspensionMaskedState(packages, false, getSendingUserId());
- } else {
- onPackageBroadcastReceived(intent, userId);
+ switch (action) {
+ case Intent.ACTION_CONFIGURATION_CHANGED:
+ onConfigurationChanged();
+ break;
+ case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
+ case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
+ synchronized (mLock) {
+ reloadWidgetsMaskedState(userId);
+ }
+ break;
+ case Intent.ACTION_PACKAGES_SUSPENDED:
+ onPackageBroadcastReceived(intent, getSendingUserId());
+ updateWidgetPackageSuspensionMaskedState(intent, true, getSendingUserId());
+ break;
+ case Intent.ACTION_PACKAGES_UNSUSPENDED:
+ onPackageBroadcastReceived(intent, getSendingUserId());
+ updateWidgetPackageSuspensionMaskedState(intent, false, getSendingUserId());
+ break;
+ default:
+ onPackageBroadcastReceived(intent, getSendingUserId());
+ break;
}
}
};
@@ -378,25 +382,32 @@
boolean changed = false;
boolean componentsModified = false;
- String pkgList[] = null;
- if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- added = true;
- } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- added = false;
- } else {
- Uri uri = intent.getData();
- if (uri == null) {
- return;
+ final String pkgList[];
+ switch (action) {
+ case Intent.ACTION_PACKAGES_SUSPENDED:
+ case Intent.ACTION_PACKAGES_UNSUSPENDED:
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ changed = true;
+ break;
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+ added = true;
+ // Follow through
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ break;
+ default: {
+ Uri uri = intent.getData();
+ if (uri == null) {
+ return;
+ }
+ String pkgName = uri.getSchemeSpecificPart();
+ if (pkgName == null) {
+ return;
+ }
+ pkgList = new String[] { pkgName };
+ added = Intent.ACTION_PACKAGE_ADDED.equals(action);
+ changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
}
- String pkgName = uri.getSchemeSpecificPart();
- if (pkgName == null) {
- return;
- }
- pkgList = new String[] { pkgName };
- added = Intent.ACTION_PACKAGE_ADDED.equals(action);
- changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
}
if (pkgList == null || pkgList.length == 0) {
return;
@@ -516,12 +527,13 @@
/**
* Incrementally update the masked state due to package suspension state.
*/
- private void updateWidgetPackageSuspensionMaskedState(String[] packagesArray, boolean suspended,
+ private void updateWidgetPackageSuspensionMaskedState(Intent intent, boolean suspended,
int profileId) {
+ String[] packagesArray = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
if (packagesArray == null) {
return;
}
- Set<String> packages = new ArraySet<String>(Arrays.asList(packagesArray));
+ Set<String> packages = new ArraySet<>(Arrays.asList(packagesArray));
synchronized (mLock) {
final int N = mProviders.size();
for (int i = 0; i < N; i++) {
@@ -2630,11 +2642,9 @@
// No file written for this user - nothing to do.
AtomicFile file = getSavedStateFile(profileId);
- try {
- FileInputStream stream = file.openRead();
+ try (FileInputStream stream = file.openRead()) {
version = readProfileStateFromFileLocked(stream, profileId, loadedWidgets);
- IoUtils.closeQuietly(stream);
- } catch (FileNotFoundException e) {
+ } catch (IOException e) {
Slog.w(TAG, "Failed to read state: " + e);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 1f4161a..caff0ba 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -447,7 +447,6 @@
android.view.autofill.Helper.sDebug = debug;
}
-
private void setVerboseLocked(boolean verbose) {
com.android.server.autofill.Helper.sVerbose = verbose;
android.view.autofill.Helper.sVerbose = verbose;
@@ -533,12 +532,13 @@
@Override
public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags,
- String packageName) {
+ ComponentName componentName) {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
autofillId = Preconditions.checkNotNull(autofillId, "autoFillId");
- packageName = Preconditions.checkNotNull(packageName, "packageName");
+ componentName = Preconditions.checkNotNull(componentName, "componentName");
+ final String packageName = Preconditions.checkNotNull(componentName.getPackageName());
Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId");
@@ -551,7 +551,7 @@
synchronized (mLock) {
final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
return service.startSessionLocked(activityToken, getCallingUid(), appCallback,
- autofillId, bounds, value, hasCallback, flags, packageName);
+ autofillId, bounds, value, hasCallback, flags, componentName);
}
}
@@ -603,7 +603,8 @@
@Override
public int updateOrRestartSession(IBinder activityToken, IBinder appCallback,
AutofillId autoFillId, Rect bounds, AutofillValue value, int userId,
- boolean hasCallback, int flags, String packageName, int sessionId, int action) {
+ boolean hasCallback, int flags, ComponentName componentName, int sessionId,
+ int action) {
boolean restart = false;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
@@ -614,7 +615,7 @@
}
if (restart) {
return startSession(activityToken, appCallback, autoFillId, bounds, value, userId,
- hasCallback, flags, packageName);
+ hasCallback, flags, componentName);
}
// Nothing changed...
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 075c741e7..d8eaccc 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -35,7 +35,6 @@
import android.content.pm.ServiceInfo;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.metrics.LogMaker;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
@@ -43,6 +42,7 @@
import android.os.Looper;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserManager;
import android.provider.Settings;
import android.service.autofill.AutofillService;
@@ -58,6 +58,7 @@
import android.util.LocalLog;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -104,6 +105,17 @@
private final LocalLog mUiLatencyHistory;
/**
+ * Apps disabled by the service; key is package name, value is when they will be enabled again.
+ */
+ private ArrayMap<String, Long> mDisabledApps;
+
+ /**
+ * Activities disabled by the service; key is component name, value is when they will be enabled
+ * again.
+ */
+ private ArrayMap<ComponentName, Long> mDisabledActivities;
+
+ /**
* Whether service was disabled for user due to {@link UserManager} restrictions.
*/
private boolean mDisabled;
@@ -286,25 +298,46 @@
int startSessionLocked(@NonNull IBinder activityToken, int uid,
@NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
@NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
- int flags, @NonNull String packageName) {
+ int flags, @NonNull ComponentName componentName) {
if (!isEnabled()) {
return 0;
}
+
+ final String shortComponentName = componentName.toShortString();
+
+ if (isAutofillDisabledLocked(componentName)) {
+ if (sDebug) {
+ Slog.d(TAG, "startSession(" + shortComponentName
+ + "): ignored because disabled by service");
+ }
+
+ final IAutoFillManagerClient client = IAutoFillManagerClient.Stub
+ .asInterface(appCallbackToken);
+ try {
+ client.setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Could not notify " + shortComponentName + " that it's disabled: " + e);
+ }
+
+ return NO_SESSION;
+ }
+
if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags);
// Occasionally clean up abandoned sessions
pruneAbandonedSessionsLocked();
final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken,
- hasCallback, packageName);
+ hasCallback, componentName);
if (newSession == null) {
return NO_SESSION;
}
final String historyItem =
- "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName
- + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" +
- hasCallback + " f=" + flags;
+ "id=" + newSession.id + " uid=" + uid + " a=" + shortComponentName
+ + " s=" + mInfo.getServiceInfo().packageName
+ + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds
+ + " hc=" + hasCallback + " f=" + flags;
mRequestsHistory.log(historyItem);
newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
@@ -395,7 +428,8 @@
}
private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
- @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull String packageName) {
+ @NonNull IBinder appCallbackToken, boolean hasCallback,
+ @NonNull ComponentName componentName) {
// use random ids so that one app cannot know that another app creates sessions
int sessionId;
int tries = 0;
@@ -411,7 +445,7 @@
final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
sessionId, uid, activityToken, appCallbackToken, hasCallback,
- mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), packageName);
+ mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), componentName);
mSessions.put(newSession.id, newSession);
return newSession;
@@ -664,6 +698,46 @@
pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
+ pw.print(prefix); pw.print("Disabled apps: ");
+
+ if (mDisabledApps == null) {
+ pw.println("N/A");
+ } else {
+ final int size = mDisabledApps.size();
+ pw.println(size);
+ final StringBuilder builder = new StringBuilder();
+ final long now = SystemClock.elapsedRealtime();
+ for (int i = 0; i < size; i++) {
+ final String packageName = mDisabledApps.keyAt(i);
+ final long expiration = mDisabledApps.valueAt(i);
+ builder.append(prefix).append(prefix)
+ .append(i).append(". ").append(packageName).append(": ");
+ TimeUtils.formatDuration((expiration - now), builder);
+ builder.append('\n');
+ }
+ pw.println(builder);
+ }
+
+ pw.print(prefix); pw.print("Disabled activities: ");
+
+ if (mDisabledActivities == null) {
+ pw.println("N/A");
+ } else {
+ final int size = mDisabledActivities.size();
+ pw.println(size);
+ final StringBuilder builder = new StringBuilder();
+ final long now = SystemClock.elapsedRealtime();
+ for (int i = 0; i < size; i++) {
+ final ComponentName component = mDisabledActivities.keyAt(i);
+ final long expiration = mDisabledActivities.valueAt(i);
+ builder.append(prefix).append(prefix)
+ .append(i).append(". ").append(component).append(": ");
+ TimeUtils.formatDuration((expiration - now), builder);
+ builder.append('\n');
+ }
+ pw.println(builder);
+ }
+
final int size = mSessions.size();
if (size == 0) {
pw.print(prefix); pw.println("No sessions");
@@ -739,7 +813,23 @@
synchronized (mLock) {
resetSession = resetClient || isClientSessionDestroyedLocked(client);
}
- client.setState(isEnabled(), resetSession, resetClient);
+ int flags = 0;
+ if (isEnabled()) {
+ flags |= AutofillManager.SET_STATE_FLAG_ENABLED;
+ }
+ if (resetSession) {
+ flags |= AutofillManager.SET_STATE_FLAG_RESET_SESSION;
+ }
+ if (resetClient) {
+ flags |= AutofillManager.SET_STATE_FLAG_RESET_CLIENT;
+ }
+ if (sDebug) {
+ flags |= AutofillManager.SET_STATE_FLAG_DEBUG;
+ }
+ if (sVerbose) {
+ flags |= AutofillManager.SET_STATE_FLAG_VERBOSE;
+ }
+ client.setState(flags);
} catch (RemoteException re) {
/* ignore */
}
@@ -764,6 +854,87 @@
return mSetupComplete && mInfo != null && !mDisabled;
}
+ /**
+ * Called by {@link Session} when service asked to disable autofill for an app.
+ */
+ void disableAutofillForApp(@NonNull String packageName, long duration) {
+ synchronized (mLock) {
+ if (mDisabledApps == null) {
+ mDisabledApps = new ArrayMap<>(1);
+ }
+ long expiration = SystemClock.elapsedRealtime() + duration;
+ // Protect it against overflow
+ if (expiration < 0) {
+ expiration = Long.MAX_VALUE;
+ }
+ mDisabledApps.put(packageName, expiration);
+ int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
+ mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP,
+ packageName, getServicePackageName())
+ .setCounterValue(intDuration));
+ }
+ }
+
+ /**
+ * Called by {@link Session} when service asked to disable autofill an app.
+ */
+ void disableAutofillForActivity(@NonNull ComponentName componentName, long duration) {
+ synchronized (mLock) {
+ if (mDisabledActivities == null) {
+ mDisabledActivities = new ArrayMap<>(1);
+ }
+ long expiration = SystemClock.elapsedRealtime() + duration;
+ // Protect it against overflow
+ if (expiration < 0) {
+ expiration = Long.MAX_VALUE;
+ }
+ mDisabledActivities.put(componentName, expiration);
+ int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
+ mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY,
+ componentName.getPackageName(), getServicePackageName())
+ .addTaggedData(MetricsEvent.FIELD_CLASS_NAME, componentName.getClassName())
+ .setCounterValue(intDuration));
+ }
+ }
+
+ /**
+ * Checks if autofill is disabled by service to the given activity.
+ */
+ private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
+ // Check activities first.
+ long elapsedTime = 0;
+ if (mDisabledActivities != null) {
+ elapsedTime = SystemClock.elapsedRealtime();
+ final Long expiration = mDisabledActivities.get(componentName);
+ if (expiration != null) {
+ if (expiration >= elapsedTime) return true;
+ // Restriction expired - clean it up.
+ if (sVerbose) {
+ Slog.v(TAG, "Removing " + componentName.toShortString() + " from disabled list");
+ }
+ mDisabledActivities.remove(componentName);
+ }
+ }
+
+ // Then check apps.
+ final String packageName = componentName.getPackageName();
+ if (mDisabledApps == null) return false;
+
+ final Long expiration = mDisabledApps.get(packageName);
+ if (expiration == null) return false;
+
+ if (elapsedTime == 0) {
+ elapsedTime = SystemClock.elapsedRealtime();
+ }
+
+ if (expiration >= elapsedTime) return true;
+
+ // Restriction expired - clean it up.
+ if (sVerbose) Slog.v(TAG, "Removing " + packageName + " from disabled list");
+ mDisabledApps.remove(packageName);
+ return false;
+ }
+
@Override
public String toString() {
return "AutofillManagerServiceImpl: [userId=" + mUserId
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 236fbfd..02a62e1 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -28,6 +28,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
@@ -112,4 +113,12 @@
}
return log;
}
+
+ public static void printlnRedactedText(@NonNull PrintWriter pw, @Nullable String text) {
+ if (text == null) {
+ pw.println("null");
+ } else {
+ pw.print(text.length()); pw.println("_chars");
+ }
+ }
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index b720f74..3564432 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -16,10 +16,10 @@
package com.android.server.autofill;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
-import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
-import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
@@ -43,6 +43,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.graphics.Bitmap;
import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.Binder;
@@ -54,14 +55,12 @@
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.InternalSanitizer;
import android.service.autofill.InternalValidator;
import android.service.autofill.SaveInfo;
import android.service.autofill.SaveRequest;
-import android.service.autofill.Transformation;
import android.service.autofill.ValueFinder;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -76,11 +75,11 @@
import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.R;
+import com.android.internal.app.IAssistDataReceiver;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.IResultReceiver;
import com.android.internal.util.ArrayUtils;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.autofill.ui.PendingUi;
@@ -91,7 +90,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -130,8 +128,8 @@
@GuardedBy("mLock")
@NonNull private IBinder mActivityToken;
- /** Package name of the app that is auto-filled */
- @NonNull private final String mPackageName;
+ /** Component that's being auto-filled */
+ @NonNull private final ComponentName mComponentName;
@GuardedBy("mLock")
private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>();
@@ -206,16 +204,16 @@
/**
* Receiver of assist data from the app's {@link Activity}.
*/
- private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
+ private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
@Override
- public void send(int resultCode, Bundle resultData) throws RemoteException {
- final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
+ public void onHandleAssistData(Bundle resultData) throws RemoteException {
+ final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE);
if (structure == null) {
Slog.e(TAG, "No assist structure - app might have crashed providing it");
return;
}
- final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS);
+ final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS);
if (receiverExtras == null) {
Slog.e(TAG, "No receiver extras - app might have crashed providing it");
return;
@@ -264,6 +262,11 @@
mRemoteFillService.onFillRequest(request);
}
+
+ @Override
+ public void onHandleAssistScreenshot(Bitmap screenshot) {
+ // Do nothing
+ }
};
/**
@@ -425,7 +428,7 @@
@NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
@NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
@NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
- @NonNull ComponentName componentName, @NonNull String packageName) {
+ @NonNull ComponentName serviceComponentName, @NonNull ComponentName componentName) {
id = sessionId;
this.uid = uid;
mStartTime = SystemClock.elapsedRealtime();
@@ -433,11 +436,11 @@
mLock = lock;
mUi = ui;
mHandlerCaller = handlerCaller;
- mRemoteFillService = new RemoteFillService(context, componentName, userId, this);
+ mRemoteFillService = new RemoteFillService(context, serviceComponentName, userId, this);
mActivityToken = activityToken;
mHasCallback = hasCallback;
mUiLatencyHistory = uiLatencyHistory;
- mPackageName = packageName;
+ mComponentName = componentName;
mClient = IAutoFillManagerClient.Stub.asInterface(client);
writeLog(MetricsEvent.AUTOFILL_SESSION_STARTED);
@@ -483,18 +486,39 @@
+ id + " destroyed");
return;
}
- }
- if (response == null) {
- processNullResponseLocked(requestFlags);
- return;
+ if (response == null) {
+ processNullResponseLocked(requestFlags);
+ return;
+ }
}
mService.setLastResponse(serviceUid, id, response);
- if ((response.getDatasets() == null || response.getDatasets().isEmpty())
- && response.getAuthentication() == null) {
+ int sessionFinishedState = 0;
+ final long disableDuration = response.getDisableDuration();
+ if (disableDuration > 0) {
+ final int flags = response.getFlags();
+ if (sDebug) {
+ final StringBuilder message = new StringBuilder("Service disabled autofill for ")
+ .append(mComponentName)
+ .append(": flags=").append(flags)
+ .append(", duration=");
+ TimeUtils.formatDuration(disableDuration, message);
+ Slog.d(TAG, message.toString());
+ }
+ if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) {
+ mService.disableAutofillForActivity(mComponentName, disableDuration);
+ } else {
+ mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration);
+ }
+ sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE;
+ }
+
+ if (((response.getDatasets() == null || response.getDatasets().isEmpty())
+ && response.getAuthentication() == null)
+ || disableDuration > 0) {
// Response is "empty" from an UI point of view, need to notify client.
- notifyUnavailableToClient(false);
+ notifyUnavailableToClient(sessionFinishedState);
}
synchronized (mLock) {
processResponseLocked(response, null, requestFlags);
@@ -1216,7 +1240,8 @@
final IAutoFillManagerClient client = getClient();
mPendingSaveUi = new PendingUi(mActivityToken, id, client);
getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
- mService.getServicePackageName(), saveInfo, valueFinder, mPackageName, this,
+ mService.getServicePackageName(), saveInfo, valueFinder,
+ mComponentName.getPackageName(), this,
mPendingSaveUi);
if (client != null) {
try {
@@ -1620,7 +1645,7 @@
}
getUiForShowing().showFillUi(filledId, response, filterText,
- mService.getServicePackageName(), mPackageName, this);
+ mService.getServicePackageName(), mComponentName.getPackageName(), this);
synchronized (mLock) {
if (mUiShownTime == 0) {
@@ -1660,14 +1685,14 @@
}
}
- private void notifyUnavailableToClient(boolean sessionFinished) {
+ private void notifyUnavailableToClient(int sessionFinishedState) {
synchronized (mLock) {
if (mCurrentViewId == null) return;
try {
if (mHasCallback) {
- mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinished);
- } else if (sessionFinished) {
- mClient.setSessionFinished(AutofillManager.STATE_FINISHED);
+ mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinishedState);
+ } else if (sessionFinishedState != 0) {
+ mClient.setSessionFinished(sessionFinishedState);
}
} catch (RemoteException e) {
Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e);
@@ -1766,7 +1791,7 @@
}
mService.resetLastResponse();
// Nothing to be done, but need to notify client.
- notifyUnavailableToClient(true);
+ notifyUnavailableToClient(AutofillManager.STATE_FINISHED);
removeSelf();
}
@@ -1964,14 +1989,14 @@
@Override
public String toString() {
- return "Session: [id=" + id + ", pkg=" + mPackageName + "]";
+ return "Session: [id=" + id + ", component=" + mComponentName + "]";
}
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
pw.print(prefix); pw.print("id: "); pw.println(id);
pw.print(prefix); pw.print("uid: "); pw.println(uid);
- pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName);
+ pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName);
pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime);
pw.print(prefix); pw.print("Time to show UI: ");
@@ -2201,7 +2226,7 @@
}
private LogMaker newLogMaker(int category, String servicePackageName) {
- return Helper.newLogMaker(category, mPackageName, servicePackageName);
+ return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName);
}
private void writeLog(int category) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 36b95fc..dc36518 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -325,14 +325,14 @@
}
/**
- * Hides all UI affordances.
+ * Hides all autofill UIs.
*/
public void hideAll(@Nullable AutoFillUiCallback callback) {
mHandler.post(() -> hideAllUiThread(callback));
}
/**
- * Destroy all UI affordances.
+ * Destroy all autofill UIs.
*/
public void destroyAll(@Nullable PendingUi pendingSaveUi,
@Nullable AutoFillUiCallback callback, boolean notifyClient) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 6d3d792..dac4586 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -49,6 +49,8 @@
import com.android.internal.R;
import com.android.server.UiThread;
+import com.android.server.autofill.Helper;
+
import libcore.util.Objects;
import java.io.PrintWriter;
@@ -466,7 +468,8 @@
pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null);
pw.print(prefix); pw.print("mListView: "); pw.println(mListView);
pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter != null);
- pw.print(prefix); pw.print("mFilterText: "); pw.println(mFilterText);
+ pw.print(prefix); pw.print("mFilterText: ");
+ Helper.printlnRedactedText(pw, mFilterText);
pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth);
pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight);
pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
diff --git a/services/autofill/java/com/android/server/autofill/ui/PendingUi.java b/services/autofill/java/com/android/server/autofill/ui/PendingUi.java
index 0851d3b..d1dfb5c 100644
--- a/services/autofill/java/com/android/server/autofill/ui/PendingUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/PendingUi.java
@@ -21,7 +21,7 @@
import android.view.autofill.IAutoFillManagerClient;
/**
- * Helper class used to handle a pending Autofill affordance such as the Save UI.
+ * Helper class used to handle a pending Autofill UI such as the save UI.
*
* <p>This class is not thread safe.
*/
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index f9213aa..622b8423 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -2877,7 +2877,7 @@
// The backend reports that our dataset has been wiped. Note this in
// the event log; the no-success code below will reset the backup
// state as well.
- EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName());
+ EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName);
}
} catch (Exception e) {
Slog.e(TAG, "Error in backup thread", e);
@@ -9781,7 +9781,8 @@
}
Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName);
- EventLog.writeEvent(EventLogTags.BACKUP_START, transport.transportDirName());
+ String transportDirName = transport.transportDirName();
+ EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
long startRealtime = SystemClock.elapsedRealtime();
int status = transport.initializeDevice();
@@ -9794,7 +9795,7 @@
Slog.i(TAG, "Device init successful");
int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
- resetBackupState(new File(mBaseStateDir, transport.transportDirName()));
+ resetBackupState(new File(mBaseStateDir, transportDirName));
EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis);
synchronized (mQueueLock) {
recordInitPendingLocked(false, transportName);
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index 7a8a920e..c0caa557 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -339,7 +339,7 @@
// The backend reports that our dataset has been wiped. Note this in
// the event log; the no-success code below will reset the backup
// state as well.
- EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName());
+ EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName);
}
} catch (Exception e) {
Slog.e(TAG, "Error in backup thread", e);
diff --git a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
index 939b1ae..690922f 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
@@ -79,7 +79,8 @@
}
Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName);
- EventLog.writeEvent(EventLogTags.BACKUP_START, transport.transportDirName());
+ String transportDirName = transport.transportDirName();
+ EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
long startRealtime = SystemClock.elapsedRealtime();
int status = transport.initializeDevice();
@@ -94,7 +95,7 @@
EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
backupManagerService
.resetBackupState(new File(backupManagerService.getBaseStateDir(),
- transport.transportDirName()));
+ transportDirName));
EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis);
synchronized (backupManagerService.getQueueLock()) {
backupManagerService.recordInitPendingLocked(false, transportName);
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 4c08f62..3904fc9 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -63,6 +63,7 @@
import android.util.SparseBooleanArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
@@ -190,7 +191,8 @@
/**
* For each uid, this is the last time we dispatched an "allow while idle" alarm,
- * used to determine the earliest we can dispatch the next such alarm.
+ * used to determine the earliest we can dispatch the next such alarm. Times are in the
+ * 'elapsed' timebase.
*/
final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
@@ -355,6 +357,22 @@
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw);
pw.println();
}
+
+ void dumpProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ proto.write(ConstantsProto.MIN_FUTURITY_DURATION_MS, MIN_FUTURITY);
+ proto.write(ConstantsProto.MIN_INTERVAL_DURATION_MS, MIN_INTERVAL);
+ proto.write(ConstantsProto.LISTENER_TIMEOUT_DURATION_MS, LISTENER_TIMEOUT);
+ proto.write(ConstantsProto.ALLOW_WHILE_IDLE_SHORT_DURATION_MS,
+ ALLOW_WHILE_IDLE_SHORT_TIME);
+ proto.write(ConstantsProto.ALLOW_WHILE_IDLE_LONG_DURATION_MS,
+ ALLOW_WHILE_IDLE_LONG_TIME);
+ proto.write(ConstantsProto.ALLOW_WHILE_IDLE_WHITELIST_DURATION_MS,
+ ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+
+ proto.end(token);
+ }
}
final Constants mConstants;
@@ -632,6 +650,20 @@
b.append('}');
return b.toString();
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed,
+ long nowRTC) {
+ final long token = proto.start(fieldId);
+
+ proto.write(BatchProto.START_REALTIME, start);
+ proto.write(BatchProto.END_REALTIME, end);
+ proto.write(BatchProto.FLAGS, flags);
+ for (Alarm a : alarms) {
+ a.writeToProto(proto, BatchProto.ALARMS, nowElapsed, nowRTC);
+ }
+
+ proto.end(token);
+ }
}
static class BatchTimeOrder implements Comparator<Batch> {
@@ -1007,6 +1039,29 @@
+ ", alarmType=" + mAlarmType
+ "}";
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ proto.write(InFlightProto.UID, mUid);
+ proto.write(InFlightProto.TAG, mTag);
+ proto.write(InFlightProto.WHEN_ELAPSED_MS, mWhenElapsed);
+ proto.write(InFlightProto.ALARM_TYPE, mAlarmType);
+ if (mPendingIntent != null) {
+ mPendingIntent.writeToProto(proto, InFlightProto.PENDING_INTENT);
+ }
+ if (mBroadcastStats != null) {
+ mBroadcastStats.writeToProto(proto, InFlightProto.BROADCAST_STATS);
+ }
+ if (mFilterStats != null) {
+ mFilterStats.writeToProto(proto, InFlightProto.FILTER_STATS);
+ }
+ if (mWorkSource != null) {
+ mWorkSource.writeToProto(proto, InFlightProto.WORK_SOURCE);
+ }
+
+ proto.end(token);
+ }
}
static final class FilterStats {
@@ -1037,6 +1092,20 @@
+ ", nesting=" + nesting
+ "}";
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ proto.write(FilterStatsProto.TAG, mTag);
+ proto.write(FilterStatsProto.LAST_FLIGHT_TIME_REALTIME, lastTime);
+ proto.write(FilterStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime);
+ proto.write(FilterStatsProto.COUNT, count);
+ proto.write(FilterStatsProto.WAKEUP_COUNT, numWakeup);
+ proto.write(FilterStatsProto.START_TIME_REALTIME, startTime);
+ proto.write(FilterStatsProto.NESTING, nesting);
+
+ proto.end(token);
+ }
}
static final class BroadcastStats {
@@ -1067,6 +1136,20 @@
+ ", nesting=" + nesting
+ "}";
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ proto.write(BroadcastStatsProto.UID, mUid);
+ proto.write(BroadcastStatsProto.PACKAGE_NAME, mPackageName);
+ proto.write(BroadcastStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime);
+ proto.write(BroadcastStatsProto.COUNT, count);
+ proto.write(BroadcastStatsProto.WAKEUP_COUNT, numWakeup);
+ proto.write(BroadcastStatsProto.START_TIME_REALTIME, startTime);
+ proto.write(BroadcastStatsProto.NESTING, nesting);
+
+ proto.end(token);
+ }
}
final SparseArray<ArrayMap<String, BroadcastStats>> mBroadcastStats
@@ -1128,14 +1211,14 @@
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent,
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
-
+
// now that we have initied the driver schedule the alarm
mClockReceiver = new ClockReceiver();
mClockReceiver.scheduleTimeTickEvent();
mClockReceiver.scheduleDateChangedEvent();
mInteractiveStateReceiver = new InteractiveStateReceiver();
mUninstallReceiver = new UninstallReceiver();
-
+
if (mNativeData != 0) {
AlarmThread waitThread = new AlarmThread();
waitThread.start();
@@ -1568,7 +1651,12 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
- dumpImpl(pw);
+
+ if (args.length > 0 && "--proto".equals(args[0])) {
+ dumpProto(fd);
+ } else {
+ dumpImpl(pw);
+ }
}
};
@@ -1681,7 +1769,7 @@
pw.print(" Idling until: ");
if (mPendingIdleUntil != null) {
pw.println(mPendingIdleUntil);
- mPendingIdleUntil.dump(pw, " ", nowRTC, nowELAPSED, sdf);
+ mPendingIdleUntil.dump(pw, " ", nowELAPSED, nowRTC, sdf);
} else {
pw.println("null");
}
@@ -1691,7 +1779,7 @@
if (mNextWakeFromIdle != null) {
pw.println();
pw.print(" Next wake from idle: "); pw.println(mNextWakeFromIdle);
- mNextWakeFromIdle.dump(pw, " ", nowRTC, nowELAPSED, sdf);
+ mNextWakeFromIdle.dump(pw, " ", nowELAPSED, nowRTC, sdf);
}
pw.println();
@@ -1760,6 +1848,7 @@
}
};
int len = 0;
+ // Get the top 10 FilterStats, ordered by aggregateTime.
for (int iu=0; iu<mBroadcastStats.size(); iu++) {
ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
for (int ip=0; ip<uidStats.size(); ip++) {
@@ -1886,6 +1975,244 @@
}
}
+ void dumpProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ synchronized (mLock) {
+ final long nowRTC = System.currentTimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ proto.write(AlarmManagerServiceProto.CURRENT_TIME, nowRTC);
+ proto.write(AlarmManagerServiceProto.ELAPSED_REALTIME, nowElapsed);
+ proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_CLOCK_TIME,
+ mLastTimeChangeClockTime);
+ proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_REALTIME,
+ mLastTimeChangeRealtime);
+
+ mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
+
+ final int foregroundUidsSize = mForegroundUids.size();
+ for (int i = 0; i < foregroundUidsSize; i++) {
+ if (mForegroundUids.valueAt(i)) {
+ proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
+ }
+ }
+ for (String pkg : mForcedAppStandbyPackages) {
+ proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
+ }
+
+ proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
+ if (!mInteractive) {
+ // Durations
+ proto.write(AlarmManagerServiceProto.TIME_SINCE_NON_INTERACTIVE_MS,
+ nowElapsed - mNonInteractiveStartTime);
+ proto.write(AlarmManagerServiceProto.MAX_WAKEUP_DELAY_MS,
+ currentNonWakeupFuzzLocked(nowElapsed));
+ proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_DISPATCH_MS,
+ nowElapsed - mLastAlarmDeliveryTime);
+ proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_DELIVERY_MS,
+ nowElapsed - mNextNonWakeupDeliveryTime);
+ }
+
+ proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_ALARM_MS,
+ mNextNonWakeup - nowElapsed);
+ proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_WAKEUP_MS,
+ mNextWakeup - nowElapsed);
+ proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_MS,
+ nowElapsed - mLastWakeup);
+ proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
+ nowElapsed - mLastWakeupSet);
+ proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
+ for (int i : mDeviceIdleUserWhitelist) {
+ proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
+ }
+
+ final TreeSet<Integer> users = new TreeSet<>();
+ final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
+ for (int i = 0; i < nextAlarmClockForUserSize; i++) {
+ users.add(mNextAlarmClockForUser.keyAt(i));
+ }
+ final int pendingSendNextAlarmClockChangedForUserSize =
+ mPendingSendNextAlarmClockChangedForUser.size();
+ for (int i = 0; i < pendingSendNextAlarmClockChangedForUserSize; i++) {
+ users.add(mPendingSendNextAlarmClockChangedForUser.keyAt(i));
+ }
+ for (int user : users) {
+ final AlarmManager.AlarmClockInfo next = mNextAlarmClockForUser.get(user);
+ final long time = next != null ? next.getTriggerTime() : 0;
+ final boolean pendingSend = mPendingSendNextAlarmClockChangedForUser.get(user);
+ final long aToken = proto.start(AlarmManagerServiceProto.NEXT_ALARM_CLOCK_METADATA);
+ proto.write(AlarmClockMetadataProto.USER, user);
+ proto.write(AlarmClockMetadataProto.IS_PENDING_SEND, pendingSend);
+ proto.write(AlarmClockMetadataProto.TRIGGER_TIME_MS, time);
+ proto.end(aToken);
+ }
+ for (Batch b : mAlarmBatches) {
+ b.writeToProto(proto, AlarmManagerServiceProto.PENDING_ALARM_BATCHES,
+ nowElapsed, nowRTC);
+ }
+ for (int i = 0; i < mPendingBackgroundAlarms.size(); i++) {
+ final ArrayList<Alarm> blockedAlarms = mPendingBackgroundAlarms.valueAt(i);
+ if (blockedAlarms != null) {
+ for (Alarm a : blockedAlarms) {
+ a.writeToProto(proto,
+ AlarmManagerServiceProto.PENDING_USER_BLOCKED_BACKGROUND_ALARMS,
+ nowElapsed, nowRTC);
+ }
+ }
+ }
+ if (mPendingIdleUntil != null) {
+ mPendingIdleUntil.writeToProto(
+ proto, AlarmManagerServiceProto.PENDING_IDLE_UNTIL, nowElapsed, nowRTC);
+ }
+ for (Alarm a : mPendingWhileIdleAlarms) {
+ a.writeToProto(proto, AlarmManagerServiceProto.PENDING_WHILE_IDLE_ALARMS,
+ nowElapsed, nowRTC);
+ }
+ if (mNextWakeFromIdle != null) {
+ mNextWakeFromIdle.writeToProto(proto, AlarmManagerServiceProto.NEXT_WAKE_FROM_IDLE,
+ nowElapsed, nowRTC);
+ }
+
+ for (Alarm a : mPendingNonWakeupAlarms) {
+ a.writeToProto(proto, AlarmManagerServiceProto.PAST_DUE_NON_WAKEUP_ALARMS,
+ nowElapsed, nowRTC);
+ }
+
+ proto.write(AlarmManagerServiceProto.DELAYED_ALARM_COUNT, mNumDelayedAlarms);
+ proto.write(AlarmManagerServiceProto.TOTAL_DELAY_TIME_MS, mTotalDelayTime);
+ proto.write(AlarmManagerServiceProto.MAX_DELAY_DURATION_MS, mMaxDelayTime);
+ proto.write(AlarmManagerServiceProto.MAX_NON_INTERACTIVE_DURATION_MS,
+ mNonInteractiveTime);
+
+ proto.write(AlarmManagerServiceProto.BROADCAST_REF_COUNT, mBroadcastRefCount);
+ proto.write(AlarmManagerServiceProto.PENDING_INTENT_SEND_COUNT, mSendCount);
+ proto.write(AlarmManagerServiceProto.PENDING_INTENT_FINISH_COUNT, mSendFinishCount);
+ proto.write(AlarmManagerServiceProto.LISTENER_SEND_COUNT, mListenerCount);
+ proto.write(AlarmManagerServiceProto.LISTENER_FINISH_COUNT, mListenerFinishCount);
+
+ for (InFlight f : mInFlight) {
+ f.writeToProto(proto, AlarmManagerServiceProto.OUTSTANDING_DELIVERIES);
+ }
+
+ proto.write(AlarmManagerServiceProto.ALLOW_WHILE_IDLE_MIN_DURATION_MS,
+ mAllowWhileIdleMinTime);
+ for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); ++i) {
+ final long token = proto.start(
+ AlarmManagerServiceProto.LAST_ALLOW_WHILE_IDLE_DISPATCH_TIMES);
+ proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.UID,
+ mLastAllowWhileIdleDispatch.keyAt(i));
+ proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.TIME_MS,
+ mLastAllowWhileIdleDispatch.valueAt(i));
+ proto.end(token);
+ }
+
+ mLog.writeToProto(proto, AlarmManagerServiceProto.RECENT_PROBLEMS);
+
+ final FilterStats[] topFilters = new FilterStats[10];
+ final Comparator<FilterStats> comparator = new Comparator<FilterStats>() {
+ @Override
+ public int compare(FilterStats lhs, FilterStats rhs) {
+ if (lhs.aggregateTime < rhs.aggregateTime) {
+ return 1;
+ } else if (lhs.aggregateTime > rhs.aggregateTime) {
+ return -1;
+ }
+ return 0;
+ }
+ };
+ int len = 0;
+ // Get the top 10 FilterStats, ordered by aggregateTime.
+ for (int iu = 0; iu < mBroadcastStats.size(); ++iu) {
+ ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
+ for (int ip = 0; ip < uidStats.size(); ++ip) {
+ BroadcastStats bs = uidStats.valueAt(ip);
+ for (int is = 0; is < bs.filterStats.size(); ++is) {
+ FilterStats fs = bs.filterStats.valueAt(is);
+ int pos = len > 0
+ ? Arrays.binarySearch(topFilters, 0, len, fs, comparator) : 0;
+ if (pos < 0) {
+ pos = -pos - 1;
+ }
+ if (pos < topFilters.length) {
+ int copylen = topFilters.length - pos - 1;
+ if (copylen > 0) {
+ System.arraycopy(topFilters, pos, topFilters, pos+1, copylen);
+ }
+ topFilters[pos] = fs;
+ if (len < topFilters.length) {
+ len++;
+ }
+ }
+ }
+ }
+ }
+ for (int i = 0; i < len; ++i) {
+ final long token = proto.start(AlarmManagerServiceProto.TOP_ALARMS);
+ FilterStats fs = topFilters[i];
+
+ proto.write(AlarmManagerServiceProto.TopAlarm.UID, fs.mBroadcastStats.mUid);
+ proto.write(AlarmManagerServiceProto.TopAlarm.PACKAGE_NAME,
+ fs.mBroadcastStats.mPackageName);
+ fs.writeToProto(proto, AlarmManagerServiceProto.TopAlarm.FILTER);
+
+ proto.end(token);
+ }
+
+ final ArrayList<FilterStats> tmpFilters = new ArrayList<FilterStats>();
+ for (int iu = 0; iu < mBroadcastStats.size(); ++iu) {
+ ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
+ for (int ip = 0; ip < uidStats.size(); ++ip) {
+ final long token = proto.start(AlarmManagerServiceProto.ALARM_STATS);
+
+ BroadcastStats bs = uidStats.valueAt(ip);
+ bs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.BROADCAST);
+
+ // uidStats is an ArrayMap, which we can't sort.
+ tmpFilters.clear();
+ for (int is = 0; is < bs.filterStats.size(); ++is) {
+ tmpFilters.add(bs.filterStats.valueAt(is));
+ }
+ Collections.sort(tmpFilters, comparator);
+ for (FilterStats fs : tmpFilters) {
+ fs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.FILTERS);
+ }
+
+ proto.end(token);
+ }
+ }
+
+ if (RECORD_DEVICE_IDLE_ALARMS) {
+ for (int i = 0; i < mAllowWhileIdleDispatches.size(); i++) {
+ IdleDispatchEntry ent = mAllowWhileIdleDispatches.get(i);
+ final long token = proto.start(
+ AlarmManagerServiceProto.ALLOW_WHILE_IDLE_DISPATCHES);
+
+ proto.write(IdleDispatchEntryProto.UID, ent.uid);
+ proto.write(IdleDispatchEntryProto.PKG, ent.pkg);
+ proto.write(IdleDispatchEntryProto.TAG, ent.tag);
+ proto.write(IdleDispatchEntryProto.OP, ent.op);
+ proto.write(IdleDispatchEntryProto.ENTRY_CREATION_REALTIME,
+ ent.elapsedRealtime);
+ proto.write(IdleDispatchEntryProto.ARG_REALTIME, ent.argRealtime);
+
+ proto.end(token);
+ }
+ }
+
+ if (WAKEUP_STATS) {
+ for (WakeupEvent event : mRecentWakeups) {
+ final long token = proto.start(AlarmManagerServiceProto.RECENT_WAKEUP_HISTORY);
+ proto.write(WakeupEventProto.UID, event.uid);
+ proto.write(WakeupEventProto.ACTION, event.action);
+ proto.write(WakeupEventProto.WHEN, event.when);
+ proto.end(token);
+ }
+ }
+ }
+
+ proto.flush();
+ }
+
private void logBatchesLocked(SimpleDateFormat sdf) {
ByteArrayOutputStream bs = new ByteArrayOutputStream(2048);
PrintWriter pw = new PrintWriter(bs);
@@ -2328,24 +2655,24 @@
alarmSeconds = when / 1000;
alarmNanoseconds = (when % 1000) * 1000 * 1000;
}
-
+
set(mNativeData, type, alarmSeconds, alarmNanoseconds);
} else {
Message msg = Message.obtain();
msg.what = ALARM_EVENT;
-
+
mHandler.removeMessages(ALARM_EVENT);
mHandler.sendMessageAtTime(msg, when);
}
}
private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list,
- String prefix, String label, long nowRTC, long nowELAPSED, SimpleDateFormat sdf) {
+ String prefix, String label, long nowELAPSED, long nowRTC, SimpleDateFormat sdf) {
for (int i=list.size()-1; i>=0; i--) {
Alarm a = list.get(i);
pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
pw.print(": "); pw.println(a);
- a.dump(pw, prefix + " ", nowRTC, nowELAPSED, sdf);
+ a.dump(pw, prefix + " ", nowELAPSED, nowRTC, sdf);
}
}
@@ -2355,8 +2682,6 @@
case RTC_WAKEUP : return "RTC_WAKEUP";
case ELAPSED_REALTIME : return "ELAPSED";
case ELAPSED_REALTIME_WAKEUP: return "ELAPSED_WAKEUP";
- default:
- break;
}
return "--unknown--";
}
@@ -2368,7 +2693,7 @@
final String label = labelForType(a.type);
pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
pw.print(": "); pw.println(a);
- a.dump(pw, prefix + " ", nowRTC, nowELAPSED, sdf);
+ a.dump(pw, prefix + " ", nowELAPSED, nowRTC, sdf);
}
}
@@ -2533,7 +2858,7 @@
return 0;
}
}
-
+
private static class Alarm {
public final int type;
public final long origWhen;
@@ -2627,7 +2952,7 @@
return sb.toString();
}
- public void dump(PrintWriter pw, String prefix, long nowRTC, long nowELAPSED,
+ public void dump(PrintWriter pw, String prefix, long nowELAPSED, long nowRTC,
SimpleDateFormat sdf) {
final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
pw.print(prefix); pw.print("tag="); pw.println(statsTag);
@@ -2656,6 +2981,30 @@
pw.print(prefix); pw.print("listener="); pw.println(listener.asBinder());
}
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed,
+ long nowRTC) {
+ final long token = proto.start(fieldId);
+
+ proto.write(AlarmProto.TAG, statsTag);
+ proto.write(AlarmProto.TYPE, type);
+ proto.write(AlarmProto.WHEN_ELAPSED_MS, whenElapsed - nowElapsed);
+ proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength);
+ proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval);
+ proto.write(AlarmProto.COUNT, count);
+ proto.write(AlarmProto.FLAGS, flags);
+ if (alarmClock != null) {
+ alarmClock.writeToProto(proto, AlarmProto.ALARM_CLOCK);
+ }
+ if (operation != null) {
+ operation.writeToProto(proto, AlarmProto.OPERATION);
+ }
+ if (listener != null) {
+ proto.write(AlarmProto.LISTENER, listener.asBinder().toString());
+ }
+
+ proto.end(token);
+ }
}
void recordWakeupAlarms(ArrayList<Batch> batches, long nowELAPSED, long nowRTC) {
@@ -2752,7 +3101,7 @@
{
super("AlarmManager");
}
-
+
public void run()
{
ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
@@ -2918,10 +3267,10 @@
public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2;
public static final int LISTENER_TIMEOUT = 3;
public static final int REPORT_ALARMS_ACTIVE = 4;
-
+
public AlarmHandler() {
}
-
+
public void handleMessage(Message msg) {
switch (msg.what) {
case ALARM_EVENT: {
@@ -2969,7 +3318,7 @@
}
}
}
-
+
class ClockReceiver extends BroadcastReceiver {
public ClockReceiver() {
IntentFilter filter = new IntentFilter();
@@ -2977,7 +3326,7 @@
filter.addAction(Intent.ACTION_DATE_CHANGED);
getContext().registerReceiver(this, filter);
}
-
+
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) {
@@ -2996,7 +3345,7 @@
scheduleDateChangedEvent();
}
}
-
+
public void scheduleTimeTickEvent() {
final long currentTime = System.currentTimeMillis();
final long nextTime = 60000 * ((currentTime / 60000) + 1);
@@ -3026,7 +3375,7 @@
Process.myUid(), "android");
}
}
-
+
class InteractiveStateReceiver extends BroadcastReceiver {
public InteractiveStateReceiver() {
IntentFilter filter = new IntentFilter();
@@ -3059,7 +3408,7 @@
sdFilter.addAction(Intent.ACTION_UID_REMOVED);
getContext().registerReceiver(this, sdFilter);
}
-
+
@Override
public void onReceive(Context context, Intent intent) {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 46b671b..ad30897 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -43,6 +43,7 @@
import android.hardware.health.V2_0.IHealth;
import android.hardware.health.V2_0.Result;
import android.os.BatteryManager;
+import android.os.BatteryManagerProto;
import android.os.BatteryManagerInternal;
import android.os.BatteryProperty;
import android.os.Binder;
@@ -252,39 +253,33 @@
mHealthServiceWrapper.init(mHealthHalCallback,
new HealthServiceWrapper.IServiceManagerSupplier() {},
new HealthServiceWrapper.IHealthSupplier() {});
- } catch (RemoteException | NoSuchElementException ex) {
- Slog.w(TAG, "health: cannot register callback. "
- + "BatteryService will be started with dummy values. Reason: "
- + ex.getClass().getSimpleName() + ": " + ex.getMessage());
- update(new HealthInfo());
- return;
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "health: cannot register callback. (RemoteException)");
+ throw ex.rethrowFromSystemServer();
+ } catch (NoSuchElementException ex) {
+ Slog.e(TAG, "health: cannot register callback. (no supported health HAL service)");
+ throw ex;
}
// init register for new service notifications, and IServiceManager should return the
// existing service in a near future. Wait for this.update() to instantiate
// the initial mHealthInfo.
- long timeWaited = 0;
+ long beforeWait = SystemClock.uptimeMillis();
synchronized (mLock) {
- long beforeWait = SystemClock.uptimeMillis();
- while (mHealthInfo == null &&
- (timeWaited = SystemClock.uptimeMillis() - beforeWait) < HEALTH_HAL_WAIT_MS) {
+ while (mHealthInfo == null) {
+ Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait) +
+ "ms for callbacks. Waiting another " + HEALTH_HAL_WAIT_MS + " ms...");
try {
- mLock.wait(HEALTH_HAL_WAIT_MS - timeWaited);
+ mLock.wait(HEALTH_HAL_WAIT_MS);
} catch (InterruptedException ex) {
- break;
+ Slog.i(TAG, "health: InterruptedException when waiting for update. "
+ + " Continuing...");
}
}
- if (mHealthInfo == null) {
- Slog.w(TAG, "health: Waited " + timeWaited + "ms for callbacks but received "
- + "nothing. BatteryService will be started with dummy values.");
- update(new HealthInfo());
- return;
- }
}
- if (DEBUG) {
- Slog.d(TAG, "health: Waited " + timeWaited + "ms and received the update.");
- }
+ Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait)
+ + "ms and received the update.");
}
private void updateBatteryWarningLevelLocked() {
@@ -913,13 +908,13 @@
synchronized (mLock) {
proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped);
- int batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_NONE;
+ int batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_NONE;
if (mHealthInfo.legacy.chargerAcOnline) {
- batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_AC;
+ batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_AC;
} else if (mHealthInfo.legacy.chargerUsbOnline) {
- batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_USB;
+ batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_USB;
} else if (mHealthInfo.legacy.chargerWirelessOnline) {
- batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_WIRELESS;
+ batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_WIRELESS;
}
proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue);
proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
@@ -1060,6 +1055,7 @@
}
public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
IHealth service = mHealthServiceWrapper.getLastService();
+ if (service == null) throw new RemoteException("no health service");
final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
switch(id) {
case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
@@ -1101,8 +1097,10 @@
}
return outResult.value;
}
- public void scheduleUpdate() {
- Slog.e(TAG, "health: must not call scheduleUpdate on battery properties");
+ public void scheduleUpdate() throws RemoteException {
+ IHealth service = mHealthServiceWrapper.getLastService();
+ if (service == null) throw new RemoteException("no health service");
+ service.update();
}
}
@@ -1165,8 +1163,10 @@
Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
private final IServiceNotification mNotification = new Notification();
+ // These variables are fixed after init.
private Callback mCallback;
private IHealthSupplier mHealthSupplier;
+ private String mInstanceName;
private final Object mLastServiceSetLock = new Object();
// Last IHealth service received.
@@ -1208,19 +1208,21 @@
IServiceManager manager = managerSupplier.get();
for (String name : sAllInstances) {
- if (manager.getTransport(IHealth.kInterfaceName, name) ==
+ if (manager.getTransport(IHealth.kInterfaceName, name) !=
IServiceManager.Transport.EMPTY) {
- continue;
+ mInstanceName = name;
+ break;
}
-
- manager.registerForNotifications(IHealth.kInterfaceName, name, mNotification);
- Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + name);
- return;
}
- throw new NoSuchElementException(String.format(
- "No IHealth service instance among %s is available. Perhaps no permission?",
- sAllInstances.toString()));
+ if (mInstanceName == null) {
+ throw new NoSuchElementException(String.format(
+ "No IHealth service instance among %s is available. Perhaps no permission?",
+ sAllInstances.toString()));
+ }
+
+ manager.registerForNotifications(IHealth.kInterfaceName, mInstanceName, mNotification);
+ Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName);
}
interface Callback {
@@ -1260,7 +1262,7 @@
public final void onRegistration(String interfaceName, String instanceName,
boolean preexisting) {
if (!IHealth.kInterfaceName.equals(interfaceName)) return;
- if (!sAllInstances.contains(instanceName)) return;
+ if (!mInstanceName.equals(instanceName)) return;
try {
// ensures the order of multiple onRegistration on different threads.
synchronized (mLastServiceSetLock) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 348c799..7e65d36 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -70,7 +70,6 @@
import android.net.RouteInfo;
import android.net.UidRange;
import android.net.Uri;
-import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.util.MultinetworkPolicyTracker;
@@ -127,6 +126,7 @@
import com.android.internal.util.XmlUtils;
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
+import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.MockableSystemProperties;
@@ -143,6 +143,7 @@
import com.android.server.net.BaseNetworkObserver;
import com.android.server.net.LockdownVpnTracker;
import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.utils.PriorityDump;
import com.google.android.collect.Lists;
@@ -682,6 +683,28 @@
}
private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
+ /**
+ * Helper class which parses out priority arguments and dumps sections according to their
+ * priority. If priority arguments are omitted, function calls the legacy dump command.
+ */
+ private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+ @Override
+ public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ doDump(fd, pw, new String[] {DIAG_ARG}, asProto);
+ doDump(fd, pw, new String[] {SHORT_ARG}, asProto);
+ }
+
+ @Override
+ public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ doDump(fd, pw, args, asProto);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ doDump(fd, pw, args, asProto);
+ }
+ };
+
public ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
this(context, netManager, statsService, policyManager, new IpConnectivityLog());
@@ -1862,8 +1885,13 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ PriorityDump.dump(mPriorityDumper, fd, writer, args);
+ }
+
+ private void doDump(FileDescriptor fd, PrintWriter writer, String[] args, boolean asProto) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ if (asProto) return;
if (argsContain(args, DIAG_ARG)) {
dumpNetworkDiagnostics(pw);
@@ -2265,7 +2293,7 @@
// Let rematchAllNetworksAndRequests() below record a new default network event
// if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence
// whose timestamps tell how long it takes to recover a default network.
- logDefaultNetworkEvent(null, nai);
+ metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(null, nai);
}
notifyIfacesChangedForNetworkStats();
// TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
@@ -4995,7 +5023,8 @@
// Notify system services that this network is up.
makeDefault(newNetwork);
// Log 0 -> X and Y -> X default network transitions, where X is the new default.
- logDefaultNetworkEvent(newNetwork, oldDefaultNetwork);
+ metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(
+ newNetwork, oldDefaultNetwork);
// Have a new default network, release the transition wakelock in
scheduleReleaseNetworkTransitionWakelock();
}
@@ -5554,25 +5583,10 @@
return ServiceManager.checkService(name) != null;
}
- private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
- int newNetid = NETID_UNSET;
- int prevNetid = NETID_UNSET;
- int[] transports = new int[0];
- boolean hadIPv4 = false;
- boolean hadIPv6 = false;
-
- if (newNai != null) {
- newNetid = newNai.network.netId;
- transports = newNai.networkCapabilities.getTransportTypes();
- }
- if (prevNai != null) {
- prevNetid = prevNai.network.netId;
- final LinkProperties lp = prevNai.linkProperties;
- hadIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
- hadIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
- }
-
- mMetricsLog.log(new DefaultNetworkEvent(newNetid, transports, prevNetid, hadIPv4, hadIPv6));
+ @VisibleForTesting
+ protected IpConnectivityMetrics.Logger metricsLogger() {
+ return checkNotNull(LocalServices.getService(IpConnectivityMetrics.Logger.class),
+ "no IpConnectivityMetrics service");
}
private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 9da3757..f007bcc 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1470,7 +1470,9 @@
broadcastFilter.addAction(ACTION_SHOW_INPUT_METHOD_PICKER);
mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
- buildInputMethodListLocked(true /* resetDefaultEnabledIme */);
+ final String defaultImiId = mSettings.getSelectedInputMethod();
+ final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
+ buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
resetDefaultImeLocked(mContext);
updateFromSettingsLocked(true);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index cf1d33c..1154fbe 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -754,7 +754,7 @@
* and re-binding, during which the system could *technically* hand that port out to someone
* else.
*/
- private void bindToRandomPort(FileDescriptor sockFd) throws IOException {
+ private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
try {
FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
@@ -763,7 +763,7 @@
Os.close(probeSocket);
Log.v(TAG, "Binding to port " + port);
Os.bind(sockFd, INADDR_ANY, port);
- return;
+ return port;
} catch (ErrnoException e) {
// Someone miraculously claimed the port just after we closed probeSocket.
if (e.errno == OsConstants.EADDRINUSE) {
@@ -803,7 +803,7 @@
Log.v(TAG, "Binding to port " + port);
Os.bind(sockFd, INADDR_ANY, port);
} else {
- bindToRandomPort(sockFd);
+ port = bindToRandomPort(sockFd);
}
// This code is common to both the unspecified and specified port cases
Os.setsockoptInt(
@@ -944,13 +944,13 @@
(c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
spi,
(auth != null) ? auth.getName() : "",
- (auth != null) ? auth.getKey() : null,
+ (auth != null) ? auth.getKey() : new byte[] {},
(auth != null) ? auth.getTruncationLengthBits() : 0,
(crypt != null) ? crypt.getName() : "",
- (crypt != null) ? crypt.getKey() : null,
+ (crypt != null) ? crypt.getKey() : new byte[] {},
(crypt != null) ? crypt.getTruncationLengthBits() : 0,
(authCrypt != null) ? authCrypt.getName() : "",
- (authCrypt != null) ? authCrypt.getKey() : null,
+ (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
(authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
encapType,
encapLocalPort,
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index cd25610..bfbce40 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -641,8 +641,8 @@
break;
}
case H_PARTITION_FORGET: {
- final String partGuid = (String) msg.obj;
- forgetPartition(partGuid);
+ final VolumeRecord rec = (VolumeRecord) msg.obj;
+ forgetPartition(rec.partGuid, rec.fsUuid);
break;
}
case H_RESET: {
@@ -1694,7 +1694,7 @@
synchronized (mLock) {
final VolumeRecord rec = mRecords.remove(fsUuid);
if (rec != null && !TextUtils.isEmpty(rec.partGuid)) {
- mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
+ mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget();
}
mCallbacks.notifyVolumeForgotten(fsUuid);
@@ -1718,7 +1718,7 @@
final String fsUuid = mRecords.keyAt(i);
final VolumeRecord rec = mRecords.valueAt(i);
if (!TextUtils.isEmpty(rec.partGuid)) {
- mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
+ mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget();
}
mCallbacks.notifyVolumeForgotten(fsUuid);
}
@@ -1733,9 +1733,9 @@
}
}
- private void forgetPartition(String partGuid) {
+ private void forgetPartition(String partGuid, String fsUuid) {
try {
- mVold.forgetPartition(partGuid);
+ mVold.forgetPartition(partGuid, fsUuid);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 8d46d1e..35f83e4 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -60,9 +60,6 @@
// Set this to true to use debug default values.
static final boolean DB = false;
- // Set this to true to have the watchdog record kernel thread stacks when it fires
- static final boolean RECORD_KERNEL_THREADS = true;
-
// Note 1: Do not lower this value below thirty seconds without tightening the invoke-with
// timeout in com.android.internal.os.ZygoteConnection, or wrapped applications
// can trigger the watchdog.
@@ -509,11 +506,6 @@
// The system's been hanging for a minute, another second or two won't hurt much.
SystemClock.sleep(2000);
- // Pull our own kernel thread stacks as well if we're configured for that
- if (RECORD_KERNEL_THREADS) {
- dumpKernelStackTraces();
- }
-
// Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log
doSysRq('w');
doSysRq('l');
@@ -591,18 +583,6 @@
}
}
- private File dumpKernelStackTraces() {
- String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
- if (tracesPath == null || tracesPath.length() == 0) {
- return null;
- }
-
- native_dumpKernelStacks(tracesPath);
- return new File(tracesPath);
- }
-
- private native void native_dumpKernelStacks(String tracesPath);
-
public static final class OpenFdMonitor {
/**
* Number of FDs below the soft limit that we trigger a runtime restart at. This was
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index c684032..0d4f5cb 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5101,8 +5101,16 @@
logStatement.bindLong(4, callingUid);
logStatement.bindString(5, tableName);
logStatement.bindLong(6, userDebugDbInsertionPoint);
- logStatement.execute();
- logStatement.clearBindings();
+ try {
+ logStatement.execute();
+ } catch (IllegalStateException e) {
+ // Guard against crash, DB can already be closed
+ // since this statement is executed on a handler thread
+ Slog.w(TAG, "Failed to insert a log record. accountId=" + accountId
+ + " action=" + action + " tableName=" + tableName + " Error: " + e);
+ } finally {
+ logStatement.clearBindings();
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 089db87..c04ddf8 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -38,7 +38,9 @@
import static com.android.server.am.proto.ActivityDisplayProto.STACKS;
import static com.android.server.am.proto.ActivityDisplayProto.ID;
+import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
import android.app.WindowConfiguration;
import android.util.IntArray;
import android.util.Slog;
@@ -206,6 +208,18 @@
}
/**
+ * Returns an existing stack compatible with the input params or creates one
+ * if a compatible stack doesn't exist.
+ * @see #getOrCreateStack(int, int, boolean)
+ */
+ <T extends ActivityStack> T getOrCreateStack(@Nullable ActivityRecord r,
+ @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, int activityType,
+ boolean onTop) {
+ final int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
+ return getOrCreateStack(windowingMode, activityType, onTop);
+ }
+
+ /**
* Creates a stack matching the input windowing mode and activity type on this display.
* @param windowingMode The windowing mode the stack should be created in. If
* {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
@@ -235,7 +249,7 @@
}
final ActivityManagerService service = mSupervisor.mService;
- if (!mSupervisor.isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
+ if (!isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement,
service.mSupportsPictureInPicture, activityType)) {
throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
@@ -252,33 +266,27 @@
}
}
- windowingMode = updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
-
final int stackId = mSupervisor.getNextStackId();
-
- final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop);
-
- if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- // Make sure recents stack exist when creating a dock stack as it normally need to be on
- // the other side of the docked stack and we make visibility decisions based on that.
- // TODO: Not sure if this is needed after we change to calculate visibility based on
- // stack z-order vs. id.
- getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
- }
-
- return stack;
+ return createStackUnchecked(windowingMode, activityType, stackId, onTop);
}
@VisibleForTesting
<T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
int stackId, boolean onTop) {
- switch (windowingMode) {
- case WINDOWING_MODE_PINNED:
- return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
- default:
- return (T) new ActivityStack(
- this, stackId, mSupervisor, windowingMode, activityType, onTop);
+ if (windowingMode == WINDOWING_MODE_PINNED) {
+ return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
}
+ final T stack = (T) new ActivityStack(
+ this, stackId, mSupervisor, windowingMode, activityType, onTop);
+
+ if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ // Make sure recents stack exist when creating a dock stack as it normally needs to be
+ // on the other side of the docked stack and we make visibility decisions based on that.
+ // TODO: Not sure if this is needed after we change to calculate visibility based on
+ // stack z-order vs. id.
+ getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
+ }
+ return stack;
}
/**
@@ -372,6 +380,105 @@
}
}
+ /**
+ * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
+ * @param windowingMode The windowing mode we are checking support for.
+ * @param supportsMultiWindow If we should consider support for multi-window mode in general.
+ * @param supportsSplitScreen If we should consider support for split-screen multi-window.
+ * @param supportsFreeform If we should consider support for freeform multi-window.
+ * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+ * @param activityType The activity type under consideration.
+ * @return true if the windowing mode is supported.
+ */
+ private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
+ boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
+ int activityType) {
+
+ if (windowingMode == WINDOWING_MODE_UNDEFINED
+ || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ return true;
+ }
+ if (!supportsMultiWindow) {
+ return false;
+ }
+
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+ return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
+ windowingMode, activityType);
+ }
+
+ if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
+ return false;
+ }
+
+ if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
+ return false;
+ }
+ return true;
+ }
+
+ int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+ @Nullable TaskRecord task, int activityType) {
+
+ // First preference if the windowing mode in the activity options if set.
+ int windowingMode = (options != null)
+ ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+ // If windowing mode is unset, then next preference is the candidate task, then the
+ // activity record.
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ if (task != null) {
+ windowingMode = task.getWindowingMode();
+ }
+ if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
+ windowingMode = r.getWindowingMode();
+ }
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ // Use the display's windowing mode.
+ windowingMode = getWindowingMode();
+ }
+ }
+
+ // Make sure the windowing mode we are trying to use makes sense for what is supported.
+ final ActivityManagerService service = mSupervisor.mService;
+ boolean supportsMultiWindow = service.mSupportsMultiWindow;
+ boolean supportsSplitScreen = service.mSupportsSplitScreenMultiWindow;
+ boolean supportsFreeform = service.mSupportsFreeformWindowManagement;
+ boolean supportsPip = service.mSupportsPictureInPicture;
+ if (supportsMultiWindow) {
+ if (task != null) {
+ supportsMultiWindow = task.isResizeable();
+ supportsSplitScreen = task.supportsSplitScreenWindowingMode();
+ // TODO: Do we need to check for freeform and Pip support here?
+ } else if (r != null) {
+ supportsMultiWindow = r.isResizeable();
+ supportsSplitScreen = r.supportsSplitScreenWindowingMode();
+ supportsFreeform = r.supportsFreeform();
+ supportsPip = r.supportsPictureInPicture();
+ }
+ }
+
+ final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
+ if (!inSplitScreenMode
+ && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+ // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
+ // trying to launch in split-screen secondary.
+ windowingMode = WINDOWING_MODE_FULLSCREEN;
+ } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
+ && supportsSplitScreen) {
+ windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+ }
+
+ if (windowingMode != WINDOWING_MODE_UNDEFINED
+ && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+ supportsFreeform, supportsPip, activityType)) {
+ return windowingMode;
+ }
+ // Return the display's windowing mode
+ return getWindowingMode();
+ }
+
/** Returns the top visible stack activity type that isn't in the exclude windowing mode. */
int getTopVisibleStackActivityType(int excludeWindowingMode) {
for (int i = mStacks.size() - 1; i >= 0; --i) {
@@ -408,6 +515,14 @@
}
}
+ /** We are in the process of exiting split-screen mode. */
+ void onExitingSplitScreenMode() {
+ // Remove reference to the primary-split-screen stack so it no longer has any effect on the
+ // display. For example, we want to be able to create fullscreen stack for standard activity
+ // types when exiting split-screen mode.
+ mSplitScreenPrimaryStack = null;
+ }
+
ActivityStack getSplitScreenPrimaryStack() {
return mSplitScreenPrimaryStack;
}
@@ -424,21 +539,6 @@
return mPinnedStack != null;
}
- int updateWindowingModeForSplitScreenIfNeeded(int windowingMode, int activityType) {
- final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
- if (!inSplitScreenMode
- && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
- // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
- // trying to launch in split-screen secondary.
- return WINDOWING_MODE_FULLSCREEN;
- } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
- && WindowConfiguration.supportSplitScreenWindowingMode(
- windowingMode, activityType)) {
- return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
- return windowingMode;
- }
-
@Override
public String toString() {
return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
@@ -504,7 +604,7 @@
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- super.writeToProto(proto, CONFIGURATION_CONTAINER);
+ super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
proto.write(ID, mDisplayId);
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = mStacks.get(stackNdx);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e1e53b3..d2d9aab 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.Manifest.permission.BIND_VOICE_INTERACTION;
import static android.Manifest.permission.CHANGE_CONFIGURATION;
import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
@@ -23,16 +24,27 @@
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
+import static android.Manifest.permission.REMOVE_TASKS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_CONTENT;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_DATA;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.app.AppOpsManager.OP_NONE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
@@ -49,9 +61,10 @@
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
import static android.os.Build.VERSION_CODES.N;
-import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL;
-import static android.os.IServiceManager.DUMP_PRIORITY_HIGH;
-import static android.os.IServiceManager.DUMP_PRIORITY_NORMAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
+import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.os.Process.BLUETOOTH_UID;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.FIRST_ISOLATED_UID;
@@ -226,6 +239,8 @@
import android.app.ProfilerInfo;
import android.app.RemoteAction;
import android.app.WaitResult;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
import android.app.admin.DevicePolicyManager;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
@@ -324,7 +339,6 @@
import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionManagerInternal;
-import android.service.voice.VoiceInteractionSession;
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -333,6 +347,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.StatsLog;
import android.util.TimingsTraceLog;
import android.util.DebugUtils;
import android.util.DisplayMetrics;
@@ -358,6 +373,7 @@
import com.android.internal.app.DumpHeapActivity;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IAssistDataReceiver;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ProcessMap;
import com.android.internal.app.SystemUserHomeActivity;
@@ -368,6 +384,7 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.BinderInternal;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.TransferPipe;
@@ -665,7 +682,7 @@
/**
* List of intents that were used to start the most recent tasks.
*/
- final RecentTasks mRecentTasks;
+ private final RecentTasks mRecentTasks;
/**
* For addAppTask: cached of the last activity component that was added.
@@ -714,30 +731,36 @@
*/
private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
@Override
- public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
- doDump(fd, pw, new String[] {"activities"});
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean asProto) {
+ if (asProto) return;
+ doDump(fd, pw, new String[]{"activities"}, asProto);
}
@Override
- public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
- doDump(fd, pw, new String[] {"settings"});
- doDump(fd, pw, new String[] {"intents"});
- doDump(fd, pw, new String[] {"broadcasts"});
- doDump(fd, pw, new String[] {"providers"});
- doDump(fd, pw, new String[] {"permissions"});
- doDump(fd, pw, new String[] {"services"});
- doDump(fd, pw, new String[] {"recents"});
- doDump(fd, pw, new String[] {"lastanr"});
- doDump(fd, pw, new String[] {"starter"});
- if (mAssociations.size() > 0) {
- doDump(fd, pw, new String[] {"associations"});
+ public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ if (asProto) {
+ doDump(fd, pw, new String[0], asProto);
+ } else {
+ doDump(fd, pw, new String[]{"settings"}, asProto);
+ doDump(fd, pw, new String[]{"intents"}, asProto);
+ doDump(fd, pw, new String[]{"broadcasts"}, asProto);
+ doDump(fd, pw, new String[]{"providers"}, asProto);
+ doDump(fd, pw, new String[]{"permissions"}, asProto);
+ doDump(fd, pw, new String[]{"services"}, asProto);
+ doDump(fd, pw, new String[]{"recents"}, asProto);
+ doDump(fd, pw, new String[]{"lastanr"}, asProto);
+ doDump(fd, pw, new String[]{"starter"}, asProto);
+ if (mAssociations.size() > 0) {
+ doDump(fd, pw, new String[]{"associations"}, asProto);
+ }
+ doDump(fd, pw, new String[]{"processes"}, asProto);
}
- doDump(fd, pw, new String[] {"processes"});
}
@Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- doDump(fd, pw, args);
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ doDump(fd, pw, args, asProto);
}
};
@@ -767,7 +790,7 @@
public final Bundle extras;
public final Intent intent;
public final String hint;
- public final IResultReceiver receiver;
+ public final IAssistDataReceiver receiver;
public final int userHandle;
public boolean haveResult = false;
public Bundle result = null;
@@ -776,7 +799,8 @@
public Bundle receiverExtras;
public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
- String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _userHandle) {
+ String _hint, IAssistDataReceiver _receiver, Bundle _receiverExtras,
+ int _userHandle) {
activity = _activity;
extras = _extras;
intent = _intent;
@@ -797,8 +821,7 @@
}
}
- final ArrayList<PendingAssistExtras> mPendingAssistExtras
- = new ArrayList<PendingAssistExtras>();
+ final ArrayList<PendingAssistExtras> mPendingAssistExtras = new ArrayList<>();
/**
* Process management.
@@ -2030,7 +2053,9 @@
synchronized (ActivityManagerService.this) {
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
ProcessRecord r = mLruProcesses.get(i);
- if (r.thread != null) {
+ // Don't dispatch to isolated processes as they can't access
+ // ConnectivityManager and don't have network privileges anyway.
+ if (r.thread != null && !r.isolated) {
try {
r.thread.setHttpProxy(host, port, exclList, pacFileUrl);
} catch (RemoteException ex) {
@@ -2483,15 +2508,15 @@
public void setSystemProcess() {
try {
ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,
- DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_NORMAL);
+ DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false,
- DUMP_PRIORITY_HIGH | DUMP_PRIORITY_NORMAL);
+ DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
ServiceManager.addService("dbinfo", new DbBinder(this));
if (MONITOR_CPU_USAGE) {
ServiceManager.addService("cpuinfo", new CpuBinder(this),
- /* allowIsolated= */ false, DUMP_PRIORITY_CRITICAL);
+ /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
}
ServiceManager.addService("permission", new PermissionController(this));
ServiceManager.addService("processinfo", new ProcessInfoService(this));
@@ -2544,7 +2569,9 @@
private final PriorityDump.PriorityDumper mPriorityDumper =
new PriorityDump.PriorityDumper() {
@Override
- public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean asProto) {
+ if (asProto) return;
mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, " ", args, false, null);
}
};
@@ -2594,7 +2621,9 @@
private final PriorityDump.PriorityDumper mPriorityDumper =
new PriorityDump.PriorityDumper() {
@Override
- public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean asProto) {
+ if (asProto) return;
if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
"cpuinfo", pw)) return;
synchronized (mActivityManagerService.mProcessCpuTracker) {
@@ -2764,7 +2793,7 @@
mTaskChangeNotificationController =
new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
mActivityStarter = new ActivityStarter(this);
- mRecentTasks = new RecentTasks(this, mStackSupervisor);
+ mRecentTasks = createRecentTasks();
mStackSupervisor.setRecentTasks(mRecentTasks);
mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
@@ -2810,6 +2839,14 @@
return new ActivityStackSupervisor(this, mHandler.getLooper());
}
+ protected RecentTasks createRecentTasks() {
+ return new RecentTasks(this, mStackSupervisor);
+ }
+
+ RecentTasks getRecentTasks() {
+ return mRecentTasks;
+ }
+
public void setSystemServiceManager(SystemServiceManager mgr) {
mSystemServiceManager = mgr;
}
@@ -3032,7 +3069,7 @@
public void batterySendBroadcast(Intent intent) {
synchronized (this) {
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
- AppOpsManager.OP_NONE, null, false, false,
+ OP_NONE, null, false, false,
-1, SYSTEM_UID, UserHandle.USER_ALL);
}
}
@@ -3145,6 +3182,7 @@
synchronized (this) {
final ActivityStack stack = mStackSupervisor.getStack(stackId);
if (stack == null) {
+ Slog.w(TAG, "setFocusedStack: No stack with id=" + stackId);
return;
}
final ActivityRecord r = stack.topRunningActivityLocked();
@@ -3181,7 +3219,8 @@
/** Sets the task stack listener that gets callbacks when a task stack changes. */
@Override
public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "registerTaskStackListener()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+ "registerTaskStackListener()");
mTaskChangeNotificationController.registerTaskStackListener(listener);
}
@@ -3190,7 +3229,8 @@
*/
@Override
public void unregisterTaskStackListener(ITaskStackListener listener) throws RemoteException {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "unregisterTaskStackListener()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+ "unregisterTaskStackListener()");
mTaskChangeNotificationController.unregisterTaskStackListener(listener);
}
@@ -4003,10 +4043,14 @@
if (DEBUG_SWITCH) Slog.d(TAG_SWITCH,
"updateUsageStats: comp=" + component + "res=" + resumed);
final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+ StatsLog.write(StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED,
+ component.userId, component.realActivity.getPackageName(),
+ component.realActivity.getShortClassName(), resumed ? 1 : 0);
if (resumed) {
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(component.realActivity, component.userId,
UsageEvents.Event.MOVE_TO_FOREGROUND);
+
}
synchronized (stats) {
stats.noteActivityResumedLocked(component.app.uid);
@@ -4051,7 +4095,7 @@
ProcessRecord app = getProcessRecordLocked(aInfo.processName,
aInfo.applicationInfo.uid, true);
if (app == null || app.instr == null) {
- intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setFlags(intent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid);
// For ANR debugging to verify if the user activity is the one that actually
// launched.
@@ -4123,7 +4167,7 @@
String lastVers = Settings.Secure.getString(
resolver, Settings.Secure.LAST_SETUP_SHOWN);
if (vers != null && !vers.equals(lastVers)) {
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(new ComponentName(
ri.activityInfo.packageName, ri.activityInfo.name));
mActivityStarter.startActivityLocked(null, intent, null /*ephemeralIntent*/,
@@ -4646,15 +4690,7 @@
Intent intent, String resolvedType, IVoiceInteractionSession session,
IVoiceInteractor interactor, int startFlags, ProfilerInfo profilerInfo,
Bundle bOptions, int userId) {
- if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: startVoiceActivity() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + android.Manifest.permission.BIND_VOICE_INTERACTION;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
+ enforceCallingPermission(BIND_VOICE_INTERACTION, "startVoiceActivity()");
if (session == null || interactor == null) {
throw new NullPointerException("null session or interactor");
}
@@ -4669,15 +4705,7 @@
@Override
public int startAssistantActivity(String callingPackage, int callingPid, int callingUid,
Intent intent, String resolvedType, Bundle bOptions, int userId) {
- if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
- != PackageManager.PERMISSION_GRANTED) {
- final String msg = "Permission Denial: startAssistantActivity() from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + Manifest.permission.BIND_VOICE_INTERACTION;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
+ enforceCallingPermission(BIND_VOICE_INTERACTION, "startAssistantActivity()");
userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
ALLOW_FULL_ONLY, "startAssistantActivity", null);
return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent,
@@ -4686,6 +4714,49 @@
}
@Override
+ public int startRecentsActivity(IAssistDataReceiver assistDataReceiver, Bundle bOptions,
+ int userId) {
+ if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
+ String msg = "Permission Denial: startRecentsActivity() from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " not recent tasks package";
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ final int recentsUid = mRecentTasks.getRecentsComponentUid();
+ final ComponentName recentsComponent = mRecentTasks.getRecentsComponent();
+ final String recentsPackage = recentsComponent.getPackageName();
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ // If provided, kick off the request for the assist data in the background before
+ // starting the activity
+ if (assistDataReceiver != null) {
+ final AppOpsManager appOpsManager = (AppOpsManager)
+ mContext.getSystemService(Context.APP_OPS_SERVICE);
+ final AssistDataReceiverProxy proxy = new AssistDataReceiverProxy(
+ assistDataReceiver, recentsPackage);
+ final AssistDataRequester requester = new AssistDataRequester(mContext, this,
+ mWindowManager, appOpsManager, proxy, this,
+ OP_ASSIST_STRUCTURE, OP_NONE);
+ requester.requestAssistData(mStackSupervisor.getTopVisibleActivities(),
+ true, false /* fetchScreenshots */, recentsUid, recentsPackage);
+ }
+
+ final Intent intent = new Intent();
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ intent.setComponent(recentsComponent);
+ return mActivityStarter.startActivityMayWait(null, recentsUid, recentsPackage,
+ intent, null, null, null, null, null, 0, 0, null, null, null, bOptions,
+ false, userId, null, "startRecentsActivity");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options)
throws RemoteException {
Slog.i(TAG, "Activity tried to startVoiceInteraction");
@@ -4831,7 +4902,7 @@
Intent.FLAG_ACTIVITY_FORWARD_RESULT|
Intent.FLAG_ACTIVITY_CLEAR_TOP|
Intent.FLAG_ACTIVITY_MULTIPLE_TASK|
- Intent.FLAG_ACTIVITY_NEW_TASK));
+ FLAG_ACTIVITY_NEW_TASK));
// Okay now we need to start the new activity, replacing the
// currently running activity. This is a little tricky because
@@ -4868,12 +4939,9 @@
@Override
public final int startActivityFromRecents(int taskId, Bundle bOptions) {
- if (checkCallingPermission(START_TASKS_FROM_RECENTS) != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: startActivityFromRecents called without " +
- START_TASKS_FROM_RECENTS;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
+ enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
+ "startActivityFromRecents()");
+
final long origId = Binder.clearCallingIdentity();
try {
synchronized (this) {
@@ -6263,7 +6331,7 @@
mStackSupervisor.closeSystemDialogsLocked();
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
- AppOpsManager.OP_NONE, null, false, false,
+ OP_NONE, null, false, false,
-1, SYSTEM_UID, UserHandle.USER_ALL);
}
@@ -6365,7 +6433,7 @@
intent.putExtra(Intent.EXTRA_UID, uid);
intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid));
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, null, 0, null, null, null, OP_NONE,
null, false, false, MY_PID, SYSTEM_UID, UserHandle.getUserId(uid));
}
@@ -8081,7 +8149,7 @@
// Adjust the source bounds by the insets for the transition down
final Rect sourceBounds = new Rect(r.pictureInPictureArgs.getSourceRectHint());
mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, aspectRatio,
- true /* moveHomeStackToFront */, "enterPictureInPictureMode");
+ "enterPictureInPictureMode");
final PinnedActivityStack stack = r.getStack();
stack.setPictureInPictureAspectRatio(aspectRatio);
stack.setPictureInPictureActions(actions);
@@ -8325,9 +8393,6 @@
}
}
- /**
- * This can be called with or without the global lock held.
- */
int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
@@ -8402,6 +8467,15 @@
}
/**
+ * This can be called with or without the global lock held.
+ */
+ void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
+ if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
+ enforceCallingPermission(permission, func);
+ }
+ }
+
+ /**
* Determine if UID is holding permissions required to access {@link Uri} in
* the given {@link ProviderInfo}. Final permission checking is always done
* in {@link ContentProvider}.
@@ -9767,25 +9841,34 @@
}
@Override
- public List<RunningTaskInfo> getTasks(int maxNum, int flags) {
+ public List<RunningTaskInfo> getTasks(int maxNum) {
+ return getFilteredTasks(maxNum, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED);
+ }
+
+ @Override
+ public List<RunningTaskInfo> getFilteredTasks(int maxNum, @ActivityType int ignoreActivityType,
+ @WindowingMode int ignoreWindowingMode) {
final int callingUid = Binder.getCallingUid();
- ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>();
+ ArrayList<RunningTaskInfo> list = new ArrayList<>();
synchronized(this) {
- if (DEBUG_ALL) Slog.v(
- TAG, "getTasks: max=" + maxNum + ", flags=" + flags);
+ if (DEBUG_ALL) Slog.v(TAG, "getTasks: max=" + maxNum);
final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(),
callingUid);
-
- // TODO: Improve with MRU list from all ActivityStacks.
- mStackSupervisor.getTasksLocked(maxNum, list, callingUid, allowed);
+ mStackSupervisor.getRunningTasks(maxNum, list, ignoreActivityType,
+ ignoreWindowingMode, callingUid, allowed);
}
return list;
}
private boolean isGetTasksAllowed(String caller, int callingPid, int callingUid) {
+ if (mRecentTasks.isCallerRecents(callingUid)) {
+ // Always allow the recents component to get tasks
+ return true;
+ }
+
boolean allowed = checkPermission(android.Manifest.permission.REAL_GET_TASKS,
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
if (!allowed) {
@@ -9833,8 +9916,7 @@
@Override
public ActivityManager.TaskDescription getTaskDescription(int id) {
synchronized (this) {
- enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
- "getTaskDescription()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getTaskDescription()");
final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id,
MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (tr != null) {
@@ -10028,7 +10110,8 @@
@Override
public void cancelTaskWindowTransition(int taskId) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "cancelTaskWindowTransition()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+ "cancelTaskWindowTransition()");
final long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
@@ -10047,7 +10130,8 @@
@Override
public void cancelTaskThumbnailTransition(int taskId) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "cancelTaskThumbnailTransition()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+ "cancelTaskThumbnailTransition()");
final long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
@@ -10066,7 +10150,7 @@
@Override
public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
- enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
+ enforceCallerIsRecentsOrHasPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
final long ident = Binder.clearCallingIdentity();
try {
final TaskRecord task;
@@ -10119,12 +10203,13 @@
@Override
public void removeStack(int stackId) {
- enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "removeStack()");
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
try {
final ActivityStack stack = mStackSupervisor.getStack(stackId);
if (stack == null) {
+ Slog.w(TAG, "removeStack: No stack with id=" + stackId);
return;
}
if (!stack.isActivityTypeStandardOrUndefined()) {
@@ -10144,7 +10229,8 @@
*/
@Override
public void removeStacksInWindowingModes(int[] windowingModes) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksInWindowingModes()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+ "removeStacksInWindowingModes()");
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
try {
@@ -10157,7 +10243,8 @@
@Override
public void removeStacksWithActivityTypes(int[] activityTypes) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksWithActivityTypes()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+ "removeStacksWithActivityTypes()");
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
try {
@@ -10186,7 +10273,7 @@
@Override
public boolean removeTask(int taskId) {
- enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, "removeTask()");
+ enforceCallerIsRecentsOrHasPermission(REMOVE_TASKS, "removeTask()");
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
try {
@@ -10229,11 +10316,7 @@
Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode");
return;
}
- final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked();
- if (prev != null) {
- task.setTaskToReturnTo(prev);
- }
- mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options, "moveTaskToFront",
+ mStackSupervisor.findTaskToMoveToFront(task, flags, options, "moveTaskToFront",
false /* forceNonResizable */);
final ActivityRecord topActivity = task.getTopActivity();
@@ -10318,7 +10401,8 @@
}
// TODO(multi-display): Have the caller pass in the windowing mode and activity type.
final ActivityStack stack = display.createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /*onTop*/);
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
+ ON_TOP);
return (stack != null) ? stack.mStackId : INVALID_STACK_ID;
}
}
@@ -10366,7 +10450,7 @@
@Override
public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
try {
@@ -10379,7 +10463,7 @@
if (DEBUG_STACK) Slog.d(TAG_STACK, "setTaskWindowingMode: moving task=" + taskId
+ " to windowingMode=" + windowingMode + " toTop=" + toTop);
if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
+ mWindowManager.setDockedStackCreateState(SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
null /* initialBounds */);
}
@@ -10402,7 +10486,7 @@
@Override
public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
@@ -10426,7 +10510,7 @@
}
if (stack.inSplitScreenPrimaryWindowingMode()) {
mWindowManager.setDockedStackCreateState(
- DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
}
task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
"moveTaskToStack");
@@ -10437,32 +10521,34 @@
}
/**
- * Moves the input task to the docked stack.
+ * Moves the input task to the primary-split-screen stack.
*
* @param taskId Id of task to move.
- * @param createMode The mode the docked stack should be created in if it doesn't exist
- * already. See
- * {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT}
+ * @param createMode The mode the primary split screen stack should be created in if it doesn't
+ * exist already. See
+ * {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT}
* and
- * {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT}
+ * {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT}
* @param toTop If the task and stack should be moved to the top.
* @param animate Whether we should play an animation for the moving the task
- * @param initialBounds If the docked stack gets created, it will use these bounds for the
- * docked stack. Pass {@code null} to use default bounds.
+ * @param initialBounds If the primary stack gets created, it will use these bounds for the
+ * stack. Pass {@code null} to use default bounds.
*/
@Override
- public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
- Rect initialBounds) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()");
+ public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+ boolean animate, Rect initialBounds) {
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+ "setTaskWindowingModeSplitScreenPrimary()");
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
if (task == null) {
- Slog.w(TAG, "moveTaskToDockedStack: No task for id=" + taskId);
+ Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId);
return false;
}
- if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
+ if (DEBUG_STACK) Slog.d(TAG_STACK,
+ "setTaskWindowingModeSplitScreenPrimary: moving task=" + taskId
+ " to createMode=" + createMode + " toTop=" + toTop);
mWindowManager.setDockedStackCreateState(createMode, initialBounds);
@@ -10475,7 +10561,7 @@
// TODO: Should just change windowing mode vs. re-parenting...
final boolean moved = task.reparent(stack, toTop,
REPARENT_KEEP_STACK_AT_FRONT, animate, !DEFER_RESUME,
- "moveTaskToDockedStack");
+ "setTaskWindowingModeSplitScreenPrimary");
if (moved) {
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
@@ -10492,12 +10578,16 @@
*/
@Override
public void dismissSplitScreenMode(boolean toTop) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
final long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
final ActivityStack stack =
mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
+ if (stack == null) {
+ Slog.w(TAG, "dismissSplitScreenMode: primary split-screen stack not found.");
+ return;
+ }
if (toTop) {
mStackSupervisor.resizeStackLocked(stack, null /* destBounds */,
null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
@@ -10520,14 +10610,14 @@
*/
@Override
public void dismissPip(boolean animate, int animationDuration) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()");
final long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
final PinnedActivityStack stack =
mStackSupervisor.getDefaultDisplay().getPinnedStack();
-
if (stack == null) {
+ Slog.w(TAG, "dismissPip: pinned stack not found.");
return;
}
if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
@@ -10557,7 +10647,8 @@
*/
@Override
public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTopActivityToPinnedStack()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+ "moveTopActivityToPinnedStack()");
synchronized (this) {
if (!mSupportsPictureInPicture) {
throw new IllegalStateException("moveTopActivityToPinnedStack:"
@@ -10576,13 +10667,14 @@
@Override
public void resizeStack(int stackId, Rect destBounds, boolean allowResizeInDockedMode,
boolean preserveWindows, boolean animate, int animationDuration) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
if (animate) {
final PinnedActivityStack stack = mStackSupervisor.getStack(stackId);
if (stack == null) {
+ Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
return;
}
if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
@@ -10611,8 +10703,7 @@
public void resizeDockedStack(Rect dockedBounds, Rect tempDockedTaskBounds,
Rect tempDockedTaskInsetBounds,
Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds) {
- enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
- "resizeDockedStack()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeDockedStack()");
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
@@ -10627,8 +10718,7 @@
@Override
public void resizePinnedStack(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
- enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
- "resizePinnedStack()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizePinnedStack()");
final long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
@@ -10687,7 +10777,7 @@
@Override
public List<StackInfo> getAllStackInfos() {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
@@ -10700,7 +10790,7 @@
@Override
public StackInfo getStackInfo(int windowingMode, int activityType) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
@@ -10743,7 +10833,21 @@
}
}
- private void startLockTaskModeLocked(@Nullable TaskRecord task, boolean isAppPinning) {
+ @Override
+ public void updateLockTaskFeatures(int userId, int flags) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != 0 && callingUid != SYSTEM_UID) {
+ enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
+ "updateLockTaskFeatures()");
+ }
+ synchronized (this) {
+ if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Allowing features " + userId + ":0x" +
+ Integer.toHexString(flags));
+ mLockTaskController.updateLockTaskFeatures(userId, flags);
+ }
+ }
+
+ private void startLockTaskModeLocked(@Nullable TaskRecord task, boolean isSystemCaller) {
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "startLockTaskModeLocked: " + task);
if (task == null || task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
return;
@@ -10757,13 +10861,16 @@
// When a task is locked, dismiss the pinned stack if it exists
mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
- // isAppPinning is used to distinguish between locked and pinned mode, as pinned mode
- // is initiated by system after the pinning request was shown and locked mode is initiated
- // by an authorized app directly
+ // {@code isSystemCaller} is used to distinguish whether this request is initiated by the
+ // system or a specific app.
+ // * System-initiated requests will only start the pinned mode (screen pinning)
+ // * App-initiated requests
+ // - will put the device in fully locked mode (LockTask), if the app is whitelisted
+ // - will start the pinned mode, otherwise
final int callingUid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
- mLockTaskController.startLockTaskMode(task, isAppPinning, callingUid);
+ mLockTaskController.startLockTaskMode(task, isSystemCaller, callingUid);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10776,7 +10883,7 @@
if (r == null) {
return;
}
- startLockTaskModeLocked(r.getTask(), false /* not system initiated */);
+ startLockTaskModeLocked(r.getTask(), false /* isSystemCaller */);
}
}
@@ -10788,7 +10895,7 @@
try {
synchronized (this) {
startLockTaskModeLocked(mStackSupervisor.anyTaskForIdLocked(taskId),
- true /* system initiated */);
+ true /* isSystemCaller */);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -10796,8 +10903,14 @@
}
@Override
- public void stopLockTaskMode() {
- stopLockTaskModeInternal(false /* not system initiated */);
+ public void stopLockTaskModeByToken(IBinder token) {
+ synchronized (this) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ if (r == null) {
+ return;
+ }
+ stopLockTaskModeInternal(r.getTask(), false /* isSystemCaller */);
+ }
}
/**
@@ -10807,15 +10920,15 @@
@Override
public void stopSystemLockTaskMode() throws RemoteException {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "stopSystemLockTaskMode");
- stopLockTaskModeInternal(true /* system initiated */);
+ stopLockTaskModeInternal(null, true /* isSystemCaller */);
}
- private void stopLockTaskModeInternal(boolean isSystemRequest) {
+ private void stopLockTaskModeInternal(@Nullable TaskRecord task, boolean isSystemCaller) {
final int callingUid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
- mLockTaskController.stopLockTaskMode(isSystemRequest, callingUid);
+ mLockTaskController.stopLockTaskMode(task, isSystemCaller, callingUid);
}
// Launch in-call UI if a call is ongoing. This is necessary to allow stopping the lock
// task and jumping straight into a call in the case of emergency call back.
@@ -11566,7 +11679,7 @@
}
final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ intent.addFlags(FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, cpi.packageName);
@@ -11861,8 +11974,7 @@
@Override
public void appNotRespondingViaProvider(IBinder connection) {
- enforceCallingPermission(
- android.Manifest.permission.REMOVE_TASKS, "appNotRespondingViaProvider()");
+ enforceCallingPermission(REMOVE_TASKS, "appNotRespondingViaProvider()");
final ContentProviderConnection conn = (ContentProviderConnection) connection;
if (conn == null) {
@@ -12330,6 +12442,9 @@
+ android.Manifest.permission.SHUTDOWN);
}
+ // TODO: Where should the corresponding '1' (start) write go?
+ StatsLog.write(StatsLog.DEVICE_ON_STATUS_CHANGED, 0);
+
boolean timedout = false;
synchronized(this) {
@@ -12873,7 +12988,7 @@
}
@Override
- public boolean requestAssistContextExtras(int requestType, IResultReceiver receiver,
+ public boolean requestAssistContextExtras(int requestType, IAssistDataReceiver receiver,
Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
@@ -12881,7 +12996,7 @@
}
@Override
- public boolean requestAutofillData(IResultReceiver receiver, Bundle receiverExtras,
+ public boolean requestAutofillData(IAssistDataReceiver receiver, Bundle receiverExtras,
IBinder activityToken, int flags) {
return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTOFILL, null, null,
receiver, receiverExtras, activityToken, true, true, UserHandle.getCallingUserId(),
@@ -12889,7 +13004,7 @@
}
private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
- IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken,
+ IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
int flags) {
enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
@@ -12957,7 +13072,7 @@
}
void pendingAssistExtrasTimedOut(PendingAssistExtras pae) {
- IResultReceiver receiver;
+ IAssistDataReceiver receiver;
synchronized (this) {
mPendingAssistExtras.remove(pae);
receiver = pae.receiver;
@@ -12966,10 +13081,9 @@
// Caller wants result sent back to them.
Bundle sendBundle = new Bundle();
// At least return the receiver extras
- sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
- pae.receiverExtras);
+ sendBundle.putBundle(ASSIST_KEY_RECEIVER_EXTRAS, pae.receiverExtras);
try {
- pae.receiver.send(0, sendBundle);
+ pae.receiver.onHandleAssistData(sendBundle);
} catch (RemoteException e) {
}
}
@@ -13007,7 +13121,7 @@
}
}
// We are now ready to launch the assist activity.
- IResultReceiver sendReceiver = null;
+ IAssistDataReceiver sendReceiver = null;
Bundle sendBundle = null;
synchronized (this) {
buildAssistBundleLocked(pae, extras);
@@ -13020,16 +13134,15 @@
if ((sendReceiver=pae.receiver) != null) {
// Caller wants result sent back to them.
sendBundle = new Bundle();
- sendBundle.putBundle(VoiceInteractionSession.KEY_DATA, pae.extras);
- sendBundle.putParcelable(VoiceInteractionSession.KEY_STRUCTURE, pae.structure);
- sendBundle.putParcelable(VoiceInteractionSession.KEY_CONTENT, pae.content);
- sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
- pae.receiverExtras);
+ sendBundle.putBundle(ASSIST_KEY_DATA, pae.extras);
+ sendBundle.putParcelable(ASSIST_KEY_STRUCTURE, pae.structure);
+ sendBundle.putParcelable(ASSIST_KEY_CONTENT, pae.content);
+ sendBundle.putBundle(ASSIST_KEY_RECEIVER_EXTRAS, pae.receiverExtras);
}
}
if (sendReceiver != null) {
try {
- sendReceiver.send(0, sendBundle);
+ sendReceiver.onHandleAssistData(sendBundle);
} catch (RemoteException e) {
}
return;
@@ -13043,7 +13156,7 @@
mContext.startServiceAsUser(pae.intent, new UserHandle(pae.userHandle));
} else {
pae.intent.replaceExtras(pae.extras);
- pae.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ pae.intent.setFlags(FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
closeSystemDialogs("assist");
@@ -13512,6 +13625,7 @@
stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
sourcePkg != null ? sourcePkg : rec.key.packageName);
pkg.noteWakeupAlarmLocked(tag);
+ StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid >= 0 ? sourceUid : uid);
}
}
}
@@ -13809,7 +13923,7 @@
.setPackage("android")
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
broadcastIntent(null, intent, null, null, 0, null, null, null,
- android.app.AppOpsManager.OP_NONE, null, true, false, UserHandle.USER_ALL);
+ OP_NONE, null, true, false, UserHandle.USER_ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -13892,6 +14006,9 @@
com.android.internal.R.string.config_appsNotReportingCrashes));
mUserController.mUserSwitchUiEnabled = !res.getBoolean(
com.android.internal.R.bool.config_customUserSwitchUi);
+ mUserController.mMaxRunningUsers = res.getInteger(
+ com.android.internal.R.integer.config_multiuserMaxRunningUsers);
+
if ((globalConfig.uiMode & UI_MODE_TYPE_TELEVISION) == UI_MODE_TYPE_TELEVISION) {
mFullscreenThumbnailScale = (float) res
.getInteger(com.android.internal.R.integer.thumbnail_width_tv) /
@@ -14060,7 +14177,7 @@
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, null, 0, null, null, null, OP_NONE,
null, false, false, MY_PID, SYSTEM_UID,
currentUserId);
intent = new Intent(Intent.ACTION_USER_STARTING);
@@ -14074,7 +14191,7 @@
throws RemoteException {
}
}, 0, null, null,
- new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+ new String[] {INTERACT_ACROSS_USERS}, OP_NONE,
null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
} catch (Throwable t) {
Slog.wtf(TAG, "Failed sending first user broadcasts", t);
@@ -14083,6 +14200,23 @@
}
mStackSupervisor.resumeFocusedStackTopActivityLocked();
mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
+
+ BinderInternal.nSetBinderProxyCountEnabled(true);
+ BinderInternal.setBinderProxyCountCallback(
+ new BinderInternal.BinderProxyLimitListener() {
+ @Override
+ public void onLimitReached(int uid) {
+ Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
+ + Process.myUid());
+ if (uid == Process.SYSTEM_UID) {
+ Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
+ } else {
+ killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
+ "Too many Binders sent to SYSTEM");
+ }
+ }
+ }, mHandler);
+
traceLog.traceEnd(); // ActivityManagerStartApps
traceLog.traceEnd(); // PhaseActivityManagerReady
}
@@ -14239,12 +14373,12 @@
}
}
sb.append("\n");
- if (info.crashInfo != null && info.crashInfo.stackTrace != null) {
- sb.append(info.crashInfo.stackTrace);
+ if (info.hasStackTrace()) {
+ sb.append(info.getStackTrace());
sb.append("\n");
}
- if (info.message != null) {
- sb.append(info.message);
+ if (info.getViolationDetails() != null) {
+ sb.append(info.getViolationDetails());
sb.append("\n");
}
@@ -14789,7 +14923,7 @@
/**
* Wrapper function to print out debug data filtered by specified arguments.
*/
- private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
boolean dumpAll = false;
@@ -14798,7 +14932,6 @@
boolean dumpCheckinFormat = false;
boolean dumpVisibleStacksOnly = false;
boolean dumpFocusedStackOnly = false;
- boolean useProto = false;
String dumpPackage = null;
int opti = 0;
@@ -14832,8 +14965,6 @@
} else if ("-h".equals(opt)) {
ActivityManagerShellCommand.dumpHelp(pw, true);
return;
- } else if ("--proto".equals(opt)) {
- useProto = true;
} else {
pw.println("Unknown argument: " + opt + "; use -h for help");
}
@@ -14900,6 +15031,19 @@
mRecentTasks.dump(pw, true /* dumpAll */, dumpPackage);
}
}
+ } else if ("binder-proxies".equals(cmd)) {
+ if (opti >= args.length) {
+ dumpBinderProxiesCounts(pw, BinderInternal.nGetBinderProxyPerUidCounts(),
+ "Counts of Binder Proxies held by SYSTEM");
+ } else {
+ String uid = args[opti];
+ opti++;
+ // Ensure Binder Proxy Count is as up to date as possible
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ pw.println(BinderInternal.nGetBinderProxyCount(Integer.parseInt(uid)));
+ }
} else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
String[] newArgs;
String name;
@@ -15418,6 +15562,34 @@
return printed;
}
+ boolean dumpBinderProxiesCounts(PrintWriter pw, SparseIntArray counts, String header) {
+ if(counts != null) {
+ pw.println(header);
+ for (int i = 0; i < counts.size(); i++) {
+ final int uid = counts.keyAt(i);
+ final int binderCount = counts.valueAt(i);
+ pw.print(" UID ");
+ pw.print(uid);
+ pw.print(", binder count = ");
+ pw.print(binderCount);
+ pw.print(", package(s)= ");
+ final String[] pkgNames = mContext.getPackageManager().getPackagesForUid(uid);
+ if (pkgNames != null) {
+ for (int j = 0; j < pkgNames.length; j++) {
+ pw.print(pkgNames[j]);
+ pw.print("; ");
+ }
+ } else {
+ pw.print("NO PACKAGE NAME FOUND");
+ }
+ pw.println();
+ }
+ pw.println();
+ return true;
+ }
+ return false;
+ }
+
void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
boolean needSep = false;
@@ -15623,7 +15795,8 @@
}
pw.println(" mPreviousProcess: " + mPreviousProcess);
}
- if (dumpAll) {
+ if (dumpAll && (mPreviousProcess == null || dumpPackage == null
+ || mPreviousProcess.pkgList.containsKey(dumpPackage))) {
StringBuilder sb = new StringBuilder(128);
sb.append(" mPreviousProcessVisibleTime: ");
TimeUtils.formatDuration(mPreviousProcessVisibleTime, sb);
@@ -15642,7 +15815,9 @@
mStackSupervisor.dumpDisplayConfigs(pw, " ");
}
if (dumpAll) {
- pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange);
+ if (dumpPackage == null) {
+ pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange);
+ }
if (mCompatModePackages.getPackages().size() > 0) {
boolean printed = false;
for (Map.Entry<String, Integer> entry
@@ -15722,8 +15897,8 @@
pw.println(" mRunningVoice=" + mRunningVoice);
pw.println(" mVoiceWakeLock" + mVoiceWakeLock);
}
+ pw.println(" mVrController=" + mVrController);
}
- pw.println(" mVrController=" + mVrController);
if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
|| mOrigWaitForDebugger) {
if (dumpPackage == null || dumpPackage.equals(mDebugApp)
@@ -16916,22 +17091,39 @@
return stringifySize(size * 1024, 1024);
}
- // Update this version number in case you change the 'compact' format
+ // Update this version number if you change the 'compact' format.
private static final int MEMINFO_COMPACT_VERSION = 1;
+ private static class MemoryUsageDumpOptions {
+ boolean dumpDetails;
+ boolean dumpFullDetails;
+ boolean dumpDalvik;
+ boolean dumpSummaryOnly;
+ boolean dumpUnreachable;
+ boolean oomOnly;
+ boolean isCompact;
+ boolean localOnly;
+ boolean packages;
+ boolean isCheckinRequest;
+ boolean dumpSwapPss;
+ boolean dumpProto;
+ }
+
final void dumpApplicationMemoryUsage(FileDescriptor fd,
PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) {
- boolean dumpDetails = false;
- boolean dumpFullDetails = false;
- boolean dumpDalvik = false;
- boolean dumpSummaryOnly = false;
- boolean dumpUnreachable = false;
- boolean oomOnly = false;
- boolean isCompact = false;
- boolean localOnly = false;
- boolean packages = false;
- boolean isCheckinRequest = false;
- boolean dumpSwapPss = false;
+ MemoryUsageDumpOptions opts = new MemoryUsageDumpOptions();
+ opts.dumpDetails = false;
+ opts.dumpFullDetails = false;
+ opts.dumpDalvik = false;
+ opts.dumpSummaryOnly = false;
+ opts.dumpUnreachable = false;
+ opts.oomOnly = false;
+ opts.isCompact = false;
+ opts.localOnly = false;
+ opts.packages = false;
+ opts.isCheckinRequest = false;
+ opts.dumpSwapPss = false;
+ opts.dumpProto = false;
int opti = 0;
while (opti < args.length) {
@@ -16941,29 +17133,31 @@
}
opti++;
if ("-a".equals(opt)) {
- dumpDetails = true;
- dumpFullDetails = true;
- dumpDalvik = true;
- dumpSwapPss = true;
+ opts.dumpDetails = true;
+ opts.dumpFullDetails = true;
+ opts.dumpDalvik = true;
+ opts.dumpSwapPss = true;
} else if ("-d".equals(opt)) {
- dumpDalvik = true;
+ opts.dumpDalvik = true;
} else if ("-c".equals(opt)) {
- isCompact = true;
+ opts.isCompact = true;
} else if ("-s".equals(opt)) {
- dumpDetails = true;
- dumpSummaryOnly = true;
+ opts.dumpDetails = true;
+ opts.dumpSummaryOnly = true;
} else if ("-S".equals(opt)) {
- dumpSwapPss = true;
+ opts.dumpSwapPss = true;
} else if ("--unreachable".equals(opt)) {
- dumpUnreachable = true;
+ opts.dumpUnreachable = true;
} else if ("--oom".equals(opt)) {
- oomOnly = true;
+ opts.oomOnly = true;
} else if ("--local".equals(opt)) {
- localOnly = true;
+ opts.localOnly = true;
} else if ("--package".equals(opt)) {
- packages = true;
+ opts.packages = true;
} else if ("--checkin".equals(opt)) {
- isCheckinRequest = true;
+ opts.isCheckinRequest = true;
+ } else if ("--proto".equals(opt)) {
+ opts.dumpProto = true;
} else if ("-h".equals(opt)) {
pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]");
@@ -16977,6 +17171,7 @@
pw.println(" --package: interpret process arg as package, dumping all");
pw.println(" processes that have loaded that package.");
pw.println(" --checkin: dump data for a checkin");
+ pw.println(" --proto: dump data to proto");
pw.println("If [process] is specified it can be the name or ");
pw.println("pid of a specific process to dump.");
return;
@@ -16985,21 +17180,33 @@
}
}
+ String[] innerArgs = new String[args.length-opti];
+ System.arraycopy(args, opti, innerArgs, 0, args.length-opti);
+
+ ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, opts.packages, args);
+ if (opts.dumpProto) {
+ dumpApplicationMemoryUsage(fd, pw, opts, innerArgs, brief, procs);
+ } else {
+ dumpApplicationMemoryUsage(fd, pw, prefix, opts, innerArgs, brief, procs, categoryPw);
+ }
+ }
+
+ final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, String prefix,
+ MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief,
+ ArrayList<ProcessRecord> procs, PrintWriter categoryPw) {
long uptime = SystemClock.uptimeMillis();
long realtime = SystemClock.elapsedRealtime();
final long[] tmpLong = new long[1];
- ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, packages, args);
if (procs == null) {
// No Java processes. Maybe they want to print a native process.
- if (args != null && args.length > opti
- && args[opti].charAt(0) != '-') {
+ if (innerArgs.length > 0 && innerArgs[0].charAt(0) != '-') {
ArrayList<ProcessCpuTracker.Stats> nativeProcs
= new ArrayList<ProcessCpuTracker.Stats>();
updateCpuStatsNow();
int findPid = -1;
try {
- findPid = Integer.parseInt(args[opti]);
+ findPid = Integer.parseInt(innerArgs[0]);
} catch (NumberFormatException e) {
}
synchronized (mProcessCpuTracker) {
@@ -17007,51 +17214,48 @@
for (int i=0; i<N; i++) {
ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
if (st.pid == findPid || (st.baseName != null
- && st.baseName.equals(args[opti]))) {
+ && st.baseName.equals(innerArgs[0]))) {
nativeProcs.add(st);
}
}
}
if (nativeProcs.size() > 0) {
- dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest,
- isCompact);
+ dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest,
+ opts.isCompact);
Debug.MemoryInfo mi = null;
for (int i = nativeProcs.size() - 1 ; i >= 0 ; i--) {
final ProcessCpuTracker.Stats r = nativeProcs.get(i);
final int pid = r.pid;
- if (!isCheckinRequest && dumpDetails) {
+ if (!opts.isCheckinRequest && opts.dumpDetails) {
pw.println("\n** MEMINFO in pid " + pid + " [" + r.baseName + "] **");
}
if (mi == null) {
mi = new Debug.MemoryInfo();
}
- if (dumpDetails || (!brief && !oomOnly)) {
+ if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
Debug.getMemoryInfo(pid, mi);
} else {
mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
mi.dalvikPrivateDirty = (int)tmpLong[0];
}
- ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
- dumpDalvik, dumpSummaryOnly, pid, r.baseName, 0, 0, 0, 0, 0, 0);
- if (isCheckinRequest) {
+ ActivityThread.dumpMemInfoTable(pw, mi, opts.isCheckinRequest, opts.dumpFullDetails,
+ opts.dumpDalvik, opts.dumpSummaryOnly, pid, r.baseName, 0, 0, 0, 0, 0, 0);
+ if (opts.isCheckinRequest) {
pw.println();
}
}
return;
}
}
- pw.println("No process found for: " + args[opti]);
+ pw.println("No process found for: " + innerArgs[0]);
return;
}
- if (!brief && !oomOnly && (procs.size() == 1 || isCheckinRequest || packages)) {
- dumpDetails = true;
+ if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) {
+ opts.dumpDetails = true;
}
- dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest, isCompact);
-
- String[] innerArgs = new String[args.length-opti];
- System.arraycopy(args, opti, innerArgs, 0, args.length-opti);
+ dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest, opts.isCompact);
ArrayList<MemItem> procMems = new ArrayList<MemItem>();
final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
@@ -17059,9 +17263,9 @@
long nativeSwapPss = 0;
long dalvikPss = 0;
long dalvikSwapPss = 0;
- long[] dalvikSubitemPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+ long[] dalvikSubitemPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
EmptyArray.LONG;
- long[] dalvikSubitemSwapPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+ long[] dalvikSubitemSwapPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
EmptyArray.LONG;
long otherPss = 0;
long otherSwapPss = 0;
@@ -17093,24 +17297,24 @@
hasActivities = r.activities.size() > 0;
}
if (thread != null) {
- if (!isCheckinRequest && dumpDetails) {
+ if (!opts.isCheckinRequest && opts.dumpDetails) {
pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
}
if (mi == null) {
mi = new Debug.MemoryInfo();
}
- if (dumpDetails || (!brief && !oomOnly)) {
+ if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
Debug.getMemoryInfo(pid, mi);
hasSwapPss = mi.hasSwappedOutPss;
} else {
mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
mi.dalvikPrivateDirty = (int)tmpLong[0];
}
- if (dumpDetails) {
- if (localOnly) {
- ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
- dumpDalvik, dumpSummaryOnly, pid, r.processName, 0, 0, 0, 0, 0, 0);
- if (isCheckinRequest) {
+ if (opts.dumpDetails) {
+ if (opts.localOnly) {
+ ActivityThread.dumpMemInfoTable(pw, mi, opts.isCheckinRequest, opts.dumpFullDetails,
+ opts.dumpDalvik, opts.dumpSummaryOnly, pid, r.processName, 0, 0, 0, 0, 0, 0);
+ if (opts.isCheckinRequest) {
pw.println();
}
} else {
@@ -17119,19 +17323,19 @@
TransferPipe tp = new TransferPipe();
try {
thread.dumpMemInfo(tp.getWriteFd(),
- mi, isCheckinRequest, dumpFullDetails,
- dumpDalvik, dumpSummaryOnly, dumpUnreachable, innerArgs);
+ mi, opts.isCheckinRequest, opts.dumpFullDetails,
+ opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, innerArgs);
tp.go(fd);
} finally {
tp.kill();
}
} catch (IOException e) {
- if (!isCheckinRequest) {
+ if (!opts.isCheckinRequest) {
pw.println("Got IoException! " + e);
pw.flush();
}
} catch (RemoteException e) {
- if (!isCheckinRequest) {
+ if (!opts.isCheckinRequest) {
pw.println("Got RemoteException! " + e);
pw.flush();
}
@@ -17150,7 +17354,7 @@
}
}
- if (!isCheckinRequest && mi != null) {
+ if (!opts.isCheckinRequest && mi != null) {
totalPss += myTotalPss;
totalSwapPss += myTotalSwapPss;
MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
@@ -17203,7 +17407,7 @@
long nativeProcTotalPss = 0;
- if (!isCheckinRequest && procs.size() > 1 && !packages) {
+ if (!opts.isCheckinRequest && procs.size() > 1 && !opts.packages) {
// If we are showing aggregations, also look for native processes to
// include so that our aggregations are more accurate.
updateCpuStatsNow();
@@ -17216,7 +17420,7 @@
if (mi == null) {
mi = new Debug.MemoryInfo();
}
- if (!brief && !oomOnly) {
+ if (!brief && !opts.oomOnly) {
Debug.getMemoryInfo(st.pid, mi);
} else {
mi.nativePss = (int)Debug.getPss(st.pid, tmpLong, null);
@@ -17303,7 +17507,7 @@
ArrayList<MemItem> oomMems = new ArrayList<MemItem>();
for (int j=0; j<oomPss.length; j++) {
if (oomPss[j] != 0) {
- String label = isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j]
+ String label = opts.isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j]
: DUMP_MEM_OOM_LABEL[j];
MemItem item = new MemItem(label, label, oomPss[j], oomSwapPss[j],
DUMP_MEM_OOM_ADJ[j]);
@@ -17312,26 +17516,26 @@
}
}
- dumpSwapPss = dumpSwapPss && hasSwapPss && totalSwapPss != 0;
- if (!brief && !oomOnly && !isCompact) {
+ opts.dumpSwapPss = opts.dumpSwapPss && hasSwapPss && totalSwapPss != 0;
+ if (!brief && !opts.oomOnly && !opts.isCompact) {
pw.println();
pw.println("Total PSS by process:");
- dumpMemItems(pw, " ", "proc", procMems, true, isCompact, dumpSwapPss);
+ dumpMemItems(pw, " ", "proc", procMems, true, opts.isCompact, opts.dumpSwapPss);
pw.println();
}
- if (!isCompact) {
+ if (!opts.isCompact) {
pw.println("Total PSS by OOM adjustment:");
}
- dumpMemItems(pw, " ", "oom", oomMems, false, isCompact, dumpSwapPss);
- if (!brief && !oomOnly) {
+ dumpMemItems(pw, " ", "oom", oomMems, false, opts.isCompact, opts.dumpSwapPss);
+ if (!brief && !opts.oomOnly) {
PrintWriter out = categoryPw != null ? categoryPw : pw;
- if (!isCompact) {
+ if (!opts.isCompact) {
out.println();
out.println("Total PSS by category:");
}
- dumpMemItems(out, " ", "cat", catMems, true, isCompact, dumpSwapPss);
+ dumpMemItems(out, " ", "cat", catMems, true, opts.isCompact, opts.dumpSwapPss);
}
- if (!isCompact) {
+ if (!opts.isCompact) {
pw.println();
}
MemInfoReader memInfo = new MemInfoReader();
@@ -17349,7 +17553,7 @@
}
}
if (!brief) {
- if (!isCompact) {
+ if (!opts.isCompact) {
pw.print("Total RAM: "); pw.print(stringifyKBSize(memInfo.getTotalSizeKb()));
pw.print(" (status ");
switch (mLastMemoryLevel) {
@@ -17390,7 +17594,7 @@
long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
- memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
- memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb();
- if (!isCompact) {
+ if (!opts.isCompact) {
pw.print(" Used RAM: "); pw.print(stringifyKBSize(totalPss - cachedPss
+ memInfo.getKernelUsedSizeKb())); pw.print(" (");
pw.print(stringifyKBSize(totalPss - cachedPss)); pw.print(" used pss + ");
@@ -17401,7 +17605,7 @@
}
if (!brief) {
if (memInfo.getZramTotalSizeKb() != 0) {
- if (!isCompact) {
+ if (!opts.isCompact) {
pw.print(" ZRAM: ");
pw.print(stringifyKBSize(memInfo.getZramTotalSizeKb()));
pw.print(" physical used for ");
@@ -17417,7 +17621,7 @@
}
}
final long[] ksm = getKsmInfo();
- if (!isCompact) {
+ if (!opts.isCompact) {
if (ksm[KSM_SHARING] != 0 || ksm[KSM_SHARED] != 0 || ksm[KSM_UNSHARED] != 0
|| ksm[KSM_VOLATILE] != 0) {
pw.print(" KSM: "); pw.print(stringifyKBSize(ksm[KSM_SHARING]));
@@ -17466,6 +17670,17 @@
}
}
+ final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw,
+ MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief,
+ ArrayList<ProcessRecord> procs) {
+ ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ // TODO: implement
+ pw.println("Not yet implemented. Have a cookie instead! :]");
+
+ proto.flush();
+ }
+
private void appendBasicMemEntry(StringBuilder sb, int oomAdj, int procState, long pss,
long memtrack, String name) {
sb.append(" ");
@@ -18060,8 +18275,7 @@
// =========================================================
@Override
- public List<ActivityManager.RunningServiceInfo> getServices(int maxNum,
- int flags) {
+ public List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags) {
enforceNotIsolatedCaller("getServices");
final int callingUid = Binder.getCallingUid();
@@ -18690,7 +18904,7 @@
Intent intent = allSticky.get(i);
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
- null, -1, -1, false, null, null, AppOpsManager.OP_NONE, null, receivers,
+ null, -1, -1, false, null, null, OP_NONE, null, receivers,
null, 0, null, null, false, true, true, -1);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
@@ -19686,7 +19900,7 @@
: new String[] {requiredPermission};
int res = broadcastIntentLocked(null, packageName, intent, resolvedType,
resultTo, resultCode, resultData, resultExtras,
- requiredPermissions, AppOpsManager.OP_NONE, bOptions, serialized,
+ requiredPermissions, OP_NONE, bOptions, serialized,
sticky, -1, uid, userId);
Binder.restoreCallingIdentity(origId);
return res;
@@ -20005,7 +20219,7 @@
@Override
public StackInfo getFocusedStackInfo() throws RemoteException {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
@@ -20045,7 +20259,8 @@
@Override
// TODO: API should just be about changing windowing modes...
public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTasksToFullscreenStack()");
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+ "moveTasksToFullscreenStack()");
synchronized (this) {
final long origId = Binder.clearCallingIdentity();
try {
@@ -20308,7 +20523,7 @@
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
- AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
UserHandle.USER_ALL);
if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
@@ -20319,7 +20534,7 @@
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
- AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
UserHandle.USER_ALL);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index f03d2d5..7eb922c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -222,6 +222,8 @@
return runSetInactive(pw);
case "get-inactive":
return runGetInactive(pw);
+ case "set-standby-bucket":
+ return runSetStandbyBucket(pw);
case "send-trim-memory":
return runSendTrimMemory(pw);
case "display":
@@ -371,7 +373,7 @@
if (mProfileFile != null || mAgent != null) {
ParcelFileDescriptor fd = null;
if (mProfileFile != null) {
- fd = openOutputFileForSystem(mProfileFile);
+ fd = openFileForSystem(mProfileFile, "w");
if (fd == null) {
return 1;
}
@@ -666,7 +668,7 @@
File file = new File(filename);
file.delete();
- ParcelFileDescriptor fd = openOutputFileForSystem(filename);
+ ParcelFileDescriptor fd = openFileForSystem(filename, "w");
if (fd == null) {
return -1;
}
@@ -754,7 +756,7 @@
if (start) {
profileFile = getNextArgRequired();
- fd = openOutputFileForSystem(profileFile);
+ fd = openFileForSystem(profileFile, "w");
if (fd == null) {
return -1;
}
@@ -818,7 +820,7 @@
File file = new File(heapFile);
file.delete();
- ParcelFileDescriptor fd = openOutputFileForSystem(heapFile);
+ ParcelFileDescriptor fd = openFileForSystem(heapFile, "w");
if (fd == null) {
return -1;
}
@@ -1824,6 +1826,27 @@
return 0;
}
+ int runSetStandbyBucket(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_CURRENT;
+
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ String packageName = getNextArgRequired();
+ String value = getNextArgRequired();
+
+ IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
+ Context.USAGE_STATS_SERVICE));
+ usm.setAppStandbyBucket(packageName, Integer.parseInt(value), userId);
+ return 0;
+ }
+
int runGetInactive(PrintWriter pw) throws RemoteException {
int userId = UserHandle.USER_CURRENT;
@@ -2282,7 +2305,7 @@
int runWrite(PrintWriter pw) {
mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
"registerUidObserver()");
- mInternal.mRecentTasks.flush();
+ mInternal.getRecentTasks().flush();
pw.println("All tasks persisted.");
return 0;
}
@@ -2453,7 +2476,10 @@
pw.println(" -e <NAME> <VALUE>: set argument <NAME> to <VALUE>. For test runners a");
pw.println(" common form is [-e <testrunner_flag> <value>[,<value>...]].");
pw.println(" -p <FILE>: write profiling data to <FILE>");
- pw.println(" -m: Write output as protobuf (machine readable)");
+ pw.println(" -m: Write output as protobuf to stdout (machine readable)");
+ pw.println(" -f <Optional PATH/TO/FILE>: Write output as protobuf to a file (machine");
+ pw.println(" readable). If path is not specified, default directory and file name will");
+ pw.println(" be used: /sdcard/instrument-logs/log-yyyyMMdd-hhmmss-SSS.instrumentation_data_proto");
pw.println(" -w: wait for instrumentation to finish before returning. Required for");
pw.println(" test runners.");
pw.println(" --user <USER_ID> | current: Specify user instrumentation runs in;");
@@ -2571,6 +2597,8 @@
pw.println(" Sets the inactive state of an app.");
pw.println(" get-inactive [--user <USER_ID>] <PACKAGE>");
pw.println(" Returns the inactive state of an app.");
+ pw.println(" set-standby-bucket [--user <USER_ID>] <PACKAGE> <BUCKET>");
+ pw.println(" Puts an app in the standby bucket.");
pw.println(" send-trim-memory [--user <USER_ID>] <PROCESS>");
pw.println(" [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]");
pw.println(" Send a memory trim event to a <PROCESS>. May also supply a raw trim int level.");
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 2c72a4d..481575e 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -77,7 +77,6 @@
import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
-import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
@@ -165,6 +164,7 @@
import android.view.IApplicationToken;
import android.view.WindowManager.LayoutParams;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.ReferrerIntent;
@@ -203,7 +203,6 @@
private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
private static final boolean SHOW_ACTIVITY_START_TIME = true;
- private static final String RECENTS_PACKAGE_NAME = "com.android.systemui.recents";
private static final String ATTR_ID = "id";
private static final String TAG_INTENT = "intent";
@@ -232,7 +231,9 @@
final String processName; // process where this component wants to run
final String taskAffinity; // as per ActivityInfo.taskAffinity
final boolean stateNotNeeded; // As per ActivityInfo.flags
- boolean fullscreen; // covers the full screen?
+ boolean fullscreen; // The activity is opaque and fills the entire space of this task.
+ // TODO: See if it possible to combine this with the fullscreen field.
+ final boolean hasWallpaper; // Has a wallpaper window as a background.
final boolean noDisplay; // activity is not displayed?
private final boolean componentSpecified; // did caller specify an explicit component?
final boolean rootVoiceInteraction; // was this the root activity of a voice interaction?
@@ -274,6 +275,7 @@
ActivityState state; // current state we are in
Bundle icicle; // last saved activity state
PersistableBundle persistentState; // last persistently saved activity state
+ // TODO: See if this is still needed.
boolean frontOfTask; // is this the root activity of its task?
boolean launchFailed; // set if a launched failed, to abort on 2nd try
boolean haveState; // have we gotten the last activity state?
@@ -883,9 +885,15 @@
Entry ent = AttributeCache.instance().get(packageName,
realTheme, com.android.internal.R.styleable.Window, userId);
- fullscreen = ent != null && !ActivityInfo.isTranslucentOrFloating(ent.array);
- noDisplay = ent != null && ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowNoDisplay, false);
+
+ if (ent != null) {
+ fullscreen = !ActivityInfo.isTranslucentOrFloating(ent.array);
+ hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
+ noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+ } else {
+ hasWallpaper = false;
+ noDisplay = false;
+ }
setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord);
@@ -1049,7 +1057,7 @@
// We only allow home activities to be resizeable if they explicitly requested it.
info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
}
- } else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) {
+ } else if (service.getRecentTasks().isRecentsComponent(realActivity, appInfo.uid)) {
activityType = ACTIVITY_TYPE_RECENTS;
} else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT
&& canLaunchAssistActivity(launchedFromPackage)) {
@@ -1553,17 +1561,7 @@
return false;
}
- boolean isVisible = !behindFullscreenActivity || mLaunchTaskBehind;
-
- if (service.mSupportsLeanbackOnly && isVisible && isActivityTypeRecents()) {
- // On devices that support leanback only (Android TV), Recents activity can only be
- // visible if the home stack is the focused stack or we are in split-screen mode.
- final ActivityDisplay display = getDisplay();
- boolean hasSplitScreenStack = display != null && display.hasSplitScreenPrimaryStack();
- isVisible = hasSplitScreenStack || mStackSupervisor.isFocusedStack(getStack());
- }
-
- return isVisible;
+ return !behindFullscreenActivity || mLaunchTaskBehind;
}
void makeVisibleIfNeeded(ActivityRecord starting) {
@@ -2065,7 +2063,7 @@
final File iconFile = new File(TaskPersister.getUserImagesDir(task.userId),
iconFilename);
final String iconFilePath = iconFile.getAbsolutePath();
- service.mRecentTasks.saveImage(icon, iconFilePath);
+ service.getRecentTasks().saveImage(icon, iconFilePath);
_taskDescription.setIconFilename(iconFilePath);
}
taskDescription = _taskDescription;
@@ -2121,11 +2119,6 @@
}
void setRequestedOrientation(int requestedOrientation) {
- if (ActivityInfo.isFixedOrientation(requestedOrientation) && !fullscreen
- && appInfo.targetSdkVersion >= O_MR1) {
- throw new IllegalStateException("Only fullscreen activities can request orientation");
- }
-
final int displayId = getDisplayId();
final Configuration displayConfig =
mStackSupervisor.getDisplayOverrideConfiguration(displayId);
@@ -2812,7 +2805,7 @@
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- super.writeToProto(proto, CONFIGURATION_CONTAINER);
+ super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
writeIdentifierToProto(proto, IDENTIFIER);
proto.write(STATE, state.toString());
proto.write(VISIBLE, visible);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 941c371..88403fc 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -16,13 +16,10 @@
package com.android.server.am;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -102,12 +99,13 @@
import android.app.AppGlobals;
import android.app.IActivityController;
import android.app.ResultInfo;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
-import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
@@ -346,7 +344,6 @@
private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
private final Rect mTmpRect2 = new Rect();
- private final Point mTmpSize = new Point();
/** Run all ActivityStacks through this */
protected final ActivityStackSupervisor mStackSupervisor;
@@ -489,26 +486,23 @@
activityType = ACTIVITY_TYPE_STANDARD;
}
final ActivityDisplay display = getDisplay();
- if (display != null) {
- if (activityType == ACTIVITY_TYPE_STANDARD
+ if (display != null && activityType == ACTIVITY_TYPE_STANDARD
&& windowingMode == WINDOWING_MODE_UNDEFINED) {
- // Standard activity types will mostly take on the windowing mode of the display if
- // one isn't specified, so look-up a compatible stack based on the display's
- // windowing mode.
- windowingMode = display.getWindowingMode();
- }
- windowingMode =
- display.updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
+ // Standard activity types will mostly take on the windowing mode of the display if one
+ // isn't specified, so look-up a compatible stack based on the display's windowing mode.
+ windowingMode = display.getWindowingMode();
}
return super.isCompatible(windowingMode, activityType);
}
/** Adds the stack to specified display and calls WindowManager to do the same. */
void reparent(ActivityDisplay activityDisplay, boolean onTop) {
+ // TODO: We should probably resolve the windowing mode for the stack on the new display here
+ // so that it end up in a compatible mode in the new display. e.g. split-screen secondary.
removeFromDisplay();
mTmpRect2.setEmpty();
postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
- adjustFocusToNextFocusableStackLocked("reparent", true /* allowFocusSelf */);
+ adjustFocusToNextFocusableStack("reparent", true /* allowFocusSelf */);
mStackSupervisor.resumeFocusedStackTopActivityLocked();
// Update visibility of activities before notifying WM. This way it won't try to resize
// windows that are no longer visible.
@@ -837,6 +831,12 @@
return mDisplayId == DEFAULT_DISPLAY;
}
+ private boolean returnsToHomeStack() {
+ return !inMultiWindowMode()
+ && !mTaskHistory.isEmpty()
+ && mTaskHistory.get(0).returnsToHomeStack();
+ }
+
void moveToFront(String reason) {
moveToFront(reason, null);
}
@@ -850,6 +850,12 @@
return;
}
+ if (!isActivityTypeHome() && returnsToHomeStack()) {
+ // Make sure the home stack is behind this stack since that is where we should return to
+ // when this stack is no longer visible.
+ mStackSupervisor.moveHomeStackToFront(reason + " returnToHome");
+ }
+
getDisplay().positionChildAtTop(this);
mStackSupervisor.setFocusStackUnchecked(reason, this);
if (task != null) {
@@ -1496,25 +1502,17 @@
}
}
- /** Returns true if the stack contains a fullscreen task. */
- private boolean hasFullscreenTask() {
- for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
- final TaskRecord task = mTaskHistory.get(i);
- if (task.mFullscreen) {
- return true;
- }
- }
- return false;
- }
-
/**
* Returns true if the stack is translucent and can have other contents visible behind it if
* needed. A stack is considered translucent if it don't contain a visible or
* starting (about to be visible) activity that is fullscreen (opaque).
* @param starting The currently starting activity or null if there is none.
- * @param stackBehind The stack directly behind this one.
*/
- private boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
+ @VisibleForTesting
+ boolean isStackTranslucent(ActivityRecord starting) {
+ if (!isAttached() || mForceHidden) {
+ return true;
+ }
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1533,21 +1531,11 @@
continue;
}
- if (r.fullscreen) {
+ if (r.fullscreen || r.hasWallpaper) {
// Stack isn't translucent if it has at least one fullscreen activity
// that is visible.
return false;
}
-
- final boolean stackBehindHomeOrRecent = stackBehind != null
- && stackBehind.isHomeOrRecentsStack();
- if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()
- && !stackBehindHomeOrRecent && !isActivityTypeAssistant()) {
- // Stack isn't translucent if it's top activity should have the home stack
- // behind it and the stack currently behind it isn't the home or recents stack
- // or the assistant stack.
- return false;
- }
}
}
return true;
@@ -1572,127 +1560,66 @@
if (!isAttached() || mForceHidden) {
return false;
}
-
- final ActivityDisplay display = getDisplay();
- if (isTopStackOnDisplay() || mStackSupervisor.isFocusedStack(this)) {
+ if (mStackSupervisor.isFocusedStack(this)) {
return true;
}
- final int stackIndex = display.getIndexOf(this);
-
- // Check position and visibility of this stack relative to the front stack on its display.
- final ActivityStack topStack = getDisplay().getTopStack();
- final int windowingMode = getWindowingMode();
- final int activityType = getActivityType();
-
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- // If the assistant stack is focused and translucent, then the docked stack is always
- // visible
- if (topStack.isActivityTypeAssistant()) {
- return topStack.isStackTranslucent(starting, this);
- }
- return true;
- }
-
- // Set home stack to invisible when it is below but not immediately below the docked stack
- // A case would be if recents stack exists but has no tasks and is below the docked stack
- // and home stack is below recents
- if (activityType == ACTIVITY_TYPE_HOME) {
- final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
- int dockedStackIndex = display.getIndexOf(splitScreenStack);
- if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) {
- return false;
- }
- }
-
- // Find the first stack behind front stack that actually got something visible.
- int stackBehindTopIndex = display.getIndexOf(topStack) - 1;
- while (stackBehindTopIndex >= 0 &&
- display.getChildAt(stackBehindTopIndex).topRunningActivityLocked() == null) {
- stackBehindTopIndex--;
- }
- final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0)
- ? display.getChildAt(stackBehindTopIndex) : null;
- int stackBehindTopWindowingMode = WINDOWING_MODE_UNDEFINED;
- int stackBehindTopActivityType = ACTIVITY_TYPE_UNDEFINED;
- if (stackBehindTop != null) {
- stackBehindTopWindowingMode = stackBehindTop.getWindowingMode();
- stackBehindTopActivityType = stackBehindTop.getActivityType();
- }
-
- final boolean alwaysOnTop = topStack.getWindowConfiguration().isAlwaysOnTop();
- if (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) {
- if (this == stackBehindTop) {
- // Stacks directly behind the docked or pinned stack are always visible.
- return true;
- } else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) {
- // Otherwise, this stack can also be visible if it is directly behind a docked stack
- // or translucent assistant stack behind an always-on-top top-most stack
- if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- return true;
- } else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) {
- return stackBehindTop.isStackTranslucent(starting, this);
- }
- }
- }
-
- if (topStack.isBackdropToTranslucentActivity()
- && topStack.isStackTranslucent(starting, stackBehindTop)) {
- // Stacks behind the fullscreen or assistant stack with a translucent activity are
- // always visible so they can act as a backdrop to the translucent activity.
- // For example, dialog activities
- if (stackIndex == stackBehindTopIndex) {
- return true;
- }
- if (stackBehindTopIndex >= 0) {
- if ((stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || stackBehindTopWindowingMode == WINDOWING_MODE_PINNED)
- && stackIndex == (stackBehindTopIndex - 1)) {
- // The stack behind the docked or pinned stack is also visible so we can have a
- // complete backdrop to the translucent activity when the docked stack is up.
- return true;
- }
- }
- }
-
- if (isOnHomeDisplay()) {
- // Visibility of any stack on default display should have been determined by the
- // conditions above.
+ final ActivityRecord top = topRunningActivityLocked();
+ if (top == null && isInStackLocked(starting) == null && !isTopStackOnDisplay()) {
+ // Shouldn't be visible if you don't have any running activities, not starting one, and
+ // not the top stack on display.
return false;
}
- final int stackCount = display.getChildCount();
- for (int i = stackIndex + 1; i < stackCount; i++) {
- final ActivityStack stack = display.getChildAt(i);
-
- if (!stack.mFullscreen && !stack.hasFullscreenTask()) {
- continue;
- }
-
- if (!stack.isDynamicStacksVisibleBehindAllowed()) {
- // These stacks can't have any dynamic stacks visible behind them.
- return false;
- }
-
- if (!stack.isStackTranslucent(starting, null /* stackBehind */)) {
- return false;
- }
- }
-
- return true;
- }
-
- private boolean isBackdropToTranslucentActivity() {
- if (isActivityTypeAssistant()) {
- return true;
- }
+ final ActivityDisplay display = getDisplay();
+ boolean gotOpaqueSplitScreenPrimary = false;
+ boolean gotOpaqueSplitScreenSecondary = false;
final int windowingMode = getWindowingMode();
- return windowingMode == WINDOWING_MODE_FULLSCREEN
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
+ for (int i = display.getChildCount() - 1; i >= 0; --i) {
+ final ActivityStack other = display.getChildAt(i);
+ if (other == this) {
+ // Should be visible if there is no other stack occluding it.
+ return true;
+ }
- private boolean isDynamicStacksVisibleBehindAllowed() {
- return isActivityTypeAssistant() || getWindowingMode() == WINDOWING_MODE_PINNED;
+ final int otherWindowingMode = other.getWindowingMode();
+ // TODO: Can be removed once we are no longer using returnToType for back functionality
+ final ActivityStack stackBehind = i > 0 ? display.getChildAt(i - 1) : null;
+
+ if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ if (other.isStackTranslucent(starting)) {
+ // Can be visible behind a translucent fullscreen stack.
+ continue;
+ }
+ return false;
+ } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ && !gotOpaqueSplitScreenPrimary) {
+ gotOpaqueSplitScreenPrimary =
+ !other.isStackTranslucent(starting);
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ && gotOpaqueSplitScreenPrimary) {
+ // Can not be visible behind another opaque stack in split-screen-primary mode.
+ return false;
+ }
+ } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ && !gotOpaqueSplitScreenSecondary) {
+ gotOpaqueSplitScreenSecondary =
+ !other.isStackTranslucent(starting);
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ && gotOpaqueSplitScreenSecondary) {
+ // Can not be visible behind another opaque stack in split-screen-secondary mode.
+ return false;
+ }
+ }
+ if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) {
+ // Can not be visible if we are in split-screen windowing mode and both halves of
+ // the screen are opaque.
+ return false;
+ }
+ }
+
+ // Well, nothing is stopping you from being visible...
+ return true;
}
final int rankTaskLayers(int baseLayer) {
@@ -1713,6 +1640,7 @@
* Make sure that all activities that need to be visible (that is, they
* currently can be seen by the user) actually are.
*/
+ // TODO: Should be re-worked based on the fact that each task as a stack in most cases.
final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges,
boolean preserveWindows) {
mTopActivityOccludesKeyguard = false;
@@ -1757,7 +1685,7 @@
visibleIgnoringKeyguard, isTop);
if (visibleIgnoringKeyguard) {
behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible,
- behindFullscreenActivity, task, r);
+ behindFullscreenActivity, r);
}
if (reallyVisible) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r
@@ -1816,16 +1744,6 @@
// show activities in the next application stack behind them vs. another
// task in the home stack like recents.
behindFullscreenActivity = true;
- } else if (windowingMode == WINDOWING_MODE_FULLSCREEN
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping after task=" + task
- + " returning to non-application type=" + task.getTaskToReturnTo());
- // Once we reach a fullscreen stack task that has a running activity and should
- // return to another stack task, then no other activities behind that one should
- // be visible.
- if (task.topRunningActivityLocked() != null && !task.returnsToStandardTask()) {
- behindFullscreenActivity = true;
- }
}
}
@@ -1861,24 +1779,6 @@
}
/**
- * Returns true if we try to maintain focus in the current stack when the top activity finishes.
- */
- private boolean keepFocusInStackIfPossible() {
- final int windowingMode = getWindowingMode();
- return windowingMode == WINDOWING_MODE_FREEFORM
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || windowingMode == WINDOWING_MODE_PINNED;
- }
-
- /**
- * Returns true if the top task in the task is allowed to return home when finished and
- * there are other tasks in the stack.
- */
- boolean allowTopTaskToReturnHome() {
- return !inPinnedWindowingMode();
- }
-
- /**
* @return the top most visible activity that wants to dismiss Keyguard
*/
ActivityRecord getTopDismissingKeyguardActivity() {
@@ -2039,18 +1939,13 @@
}
private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity,
- TaskRecord task, ActivityRecord r) {
+ ActivityRecord r) {
if (r.fullscreen) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
+ " stackInvisible=" + stackInvisible
+ " behindFullscreenActivity=" + behindFullscreenActivity);
// At this point, nothing else needs to be shown in this task.
behindFullscreenActivity = true;
- } else if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
- + " stackInvisible=" + stackInvisible
- + " behindFullscreenActivity=" + behindFullscreenActivity);
- behindFullscreenActivity = true;
}
return behindFullscreenActivity;
}
@@ -2209,7 +2104,7 @@
final boolean hasRunningActivity = next != null;
// TODO: Maybe this entire condition can get removed?
- if (hasRunningActivity && getDisplay() == null) {
+ if (hasRunningActivity && !isAttached()) {
return false;
}
@@ -2239,28 +2134,6 @@
return false;
}
- final TaskRecord nextTask = next.getTask();
- final TaskRecord prevTask = prev != null ? prev.getTask() : null;
- if (prevTask != null && prevTask.getStack() == this &&
- prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) {
- if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
- if (prevTask == nextTask) {
- prevTask.setFrontOfTask();
- } else if (prevTask != topTask()) {
- // This task is going away but it was supposed to return to the home stack.
- // Now the task above it has to return to the home task instead.
- final int taskNdx = mTaskHistory.indexOf(prevTask) + 1;
- mTaskHistory.get(taskNdx).setTaskToReturnTo(ACTIVITY_TYPE_HOME);
- } else if (!isOnHomeDisplay()) {
- return false;
- } else if (!isActivityTypeHome()){
- if (DEBUG_STATES) Slog.d(TAG_STATES,
- "resumeTopActivityLocked: Launching home next");
- return isOnHomeDisplay() &&
- mStackSupervisor.resumeHomeStackTask(prev, "prevFinished");
- }
- }
-
// If we are sleeping, and there is no resumed activity, and the top
// activity is paused, well that is the state we want.
if (shouldSleepOrShutDownActivities()
@@ -2644,7 +2517,7 @@
private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev,
ActivityOptions options, String reason) {
- if (adjustFocusToNextFocusableStackLocked(reason)) {
+ if (adjustFocusToNextFocusableStack(reason)) {
// Try to move focus to the next visible stack with a running activity if this
// stack is not covering the entire screen or is on a secondary display (with no home
// stack).
@@ -2718,7 +2591,6 @@
}
private void insertTaskAtTop(TaskRecord task, ActivityRecord starting) {
- updateTaskReturnToForTopInsertion(task);
// TODO: Better place to put all the code below...may be addTask...
mTaskHistory.remove(task);
// Now put task at top.
@@ -2729,57 +2601,6 @@
true /* includingParents */);
}
- /**
- * Updates the {@param task}'s return type before it is moved to the top.
- */
- private void updateTaskReturnToForTopInsertion(TaskRecord task) {
- boolean isLastTaskOverHome = false;
- // If the moving task is over the home or assistant stack, transfer its return type to next
- // task so that they return to the same stack
- if (task.isOverHomeStack() || task.isOverAssistantStack()) {
- final TaskRecord nextTask = getNextTask(task);
- if (nextTask != null) {
- nextTask.setTaskToReturnTo(task.getTaskToReturnTo());
- } else {
- isLastTaskOverHome = true;
- }
- }
-
- // If this is not on the default display, then just set the return type to application
- if (!isOnHomeDisplay()) {
- task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
- return;
- }
-
- final ActivityStack lastStack = mStackSupervisor.getLastStack();
-
- // If there is no last task, do not set task to return to
- if (lastStack == null) {
- return;
- }
-
- // If the task was launched from the assistant stack, set the return type to assistant
- if (lastStack.isActivityTypeAssistant()) {
- task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
- return;
- }
-
- // If this is being moved to the top by another activity or being launched from the home
- // activity, set mTaskToReturnTo accordingly.
- final boolean fromHomeOrRecents = lastStack.isHomeOrRecentsStack();
- final TaskRecord topTask = lastStack.topTask();
- if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask() != task)) {
- // If it's a last task over home - we default to keep its return to type not to
- // make underlying task focused when this one will be finished.
- int returnToType = isLastTaskOverHome
- ? task.getTaskToReturnTo() : ACTIVITY_TYPE_STANDARD;
- if (fromHomeOrRecents && allowTopTaskToReturnHome()) {
- returnToType = topTask == null ? ACTIVITY_TYPE_HOME : topTask.getActivityType();
- }
- task.setTaskToReturnTo(returnToType);
- }
- }
-
final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
boolean newTask, boolean keepCurTransition, ActivityOptions options) {
TaskRecord rTask = r.getTask();
@@ -3049,7 +2870,7 @@
}
mWindowContainerController.positionChildAtBottom(
- targetTask.getWindowContainerController());
+ targetTask.getWindowContainerController(), false /* includingParents */);
replyChainEnd = -1;
} else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) {
// If the activity should just be removed -- either
@@ -3285,7 +3106,7 @@
}
/** Returns true if the task is one of the task finishing on-top of the top running task. */
- boolean isATopFinishingTask(TaskRecord task) {
+ private boolean isATopFinishingTask(TaskRecord task) {
for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
final TaskRecord current = mTaskHistory.get(i);
final ActivityRecord r = current.topRunningActivityLocked();
@@ -3300,7 +3121,7 @@
return false;
}
- private void adjustFocusedActivityStackLocked(ActivityRecord r, String reason) {
+ private void adjustFocusedActivityStack(ActivityRecord r, String reason) {
if (!mStackSupervisor.isFocusedStack(this) ||
((mResumedActivity != r) && (mResumedActivity != null))) {
return;
@@ -3309,66 +3130,44 @@
final ActivityRecord next = topRunningActivityLocked();
final String myReason = reason + " adjustFocus";
- if (next != r) {
- if (next != null && keepFocusInStackIfPossible() && isFocusable()) {
- // For freeform, docked, and pinned stacks we always keep the focus within the
- // stack as long as there is a running activity.
- return;
- } else {
- // Task is not guaranteed to be non-null. For example, destroying the
- // {@link ActivityRecord} will disassociate the task from the activity.
- final TaskRecord task = r.getTask();
-
- if (task == null) {
- throw new IllegalStateException("activity no longer associated with task:" + r);
- }
-
- final boolean isAssistantOrOverAssistant =
- task.getStack().isActivityTypeAssistant() || task.isOverAssistantStack();
- if (r.frontOfTask && isATopFinishingTask(task)
- && (task.isOverHomeStack() || isAssistantOrOverAssistant)) {
- // For non-fullscreen or assistant stack, we want to move the focus to the next
- // visible stack to prevent the home screen from moving to the top and obscuring
- // other visible stacks.
- if ((!mFullscreen || isAssistantOrOverAssistant)
- && adjustFocusToNextFocusableStackLocked(myReason)) {
- return;
- }
- // Move the home stack to the top if this stack is fullscreen or there is no
- // other visible stack.
- if (task.isOverHomeStack() &&
- mStackSupervisor.moveHomeStackTaskToTop(myReason)) {
- // Activity focus was already adjusted. Nothing else to do...
- return;
- }
- }
- }
+ if (next == r) {
+ mStackSupervisor.moveFocusableActivityStackToFrontLocked(
+ mStackSupervisor.topRunningActivityLocked(), myReason);
+ return;
}
- mStackSupervisor.moveFocusableActivityStackToFrontLocked(
- mStackSupervisor.topRunningActivityLocked(), myReason);
+ if (next != null && isFocusable()) {
+ // Keep focus in stack if we have a top running activity and are focusable.
+ return;
+ }
+
+ // Task is not guaranteed to be non-null. For example, destroying the
+ // {@link ActivityRecord} will disassociate the task from the activity.
+ final TaskRecord task = r.getTask();
+
+ if (task == null) {
+ throw new IllegalStateException("activity no longer associated with task:" + r);
+ }
+
+ // Move focus to next focusable stack if possible.
+ if (adjustFocusToNextFocusableStack(myReason)) {
+ return;
+ }
+
+ // Whatever...go home.
+ mStackSupervisor.moveHomeStackTaskToTop(myReason);
}
/** Find next proper focusable stack and make it focused. */
- private boolean adjustFocusToNextFocusableStackLocked(String reason) {
- return adjustFocusToNextFocusableStackLocked(reason, false /* allowFocusSelf */);
+ private boolean adjustFocusToNextFocusableStack(String reason) {
+ return adjustFocusToNextFocusableStack(reason, false /* allowFocusSelf */);
}
/**
* Find next proper focusable stack and make it focused.
* @param allowFocusSelf Is the focus allowed to remain on the same stack.
*/
- private boolean adjustFocusToNextFocusableStackLocked(String reason, boolean allowFocusSelf) {
- if (isActivityTypeAssistant() && bottomTask() != null
- && bottomTask().returnsToHomeTask()) {
- // If the current stack is the assistant stack, then use the return-to type to determine
- // whether to return to the home screen. This is needed to workaround an issue where
- // launching a fullscreen task (and subequently returning from that task) will cause
- // the fullscreen stack to be found as the next focusable stack below, even if the
- // assistant was launched over home.
- return mStackSupervisor.moveHomeStackTaskToTop(reason);
- }
-
+ private boolean adjustFocusToNextFocusableStack(String reason, boolean allowFocusSelf) {
final ActivityStack stack = mStackSupervisor.getNextFocusableStackLocked(
allowFocusSelf ? null : this);
final String myReason = reason + " adjustFocusToNextFocusableStack";
@@ -3378,22 +3177,12 @@
final ActivityRecord top = stack.topRunningActivityLocked();
- if (stack.isHomeOrRecentsStack() && (top == null || !top.visible)) {
+ if (stack.isActivityTypeHome() && (top == null || !top.visible)) {
// If we will be focusing on the home stack next and its current top activity isn't
- // visible, then use the task return to value to determine the home task to display
- // next.
+ // visible, then use the move the home stack task to top to make the activity visible.
return mStackSupervisor.moveHomeStackTaskToTop(reason);
}
- if (stack.isActivityTypeAssistant() && top != null
- && top.getTask().returnsToHomeTask()) {
- // It is possible for the home stack to not be directly underneath the assistant stack.
- // For example, the assistant may start an activity in the fullscreen stack. Upon
- // returning to the assistant stack, we must ensure that the home stack is underneath
- // when appropriate.
- mStackSupervisor.moveHomeStackTaskToTop("adjustAssistantReturnToHome");
- }
-
stack.moveToFront(myReason);
return true;
}
@@ -3408,7 +3197,7 @@
if (requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
"stop-no-history", false)) {
// If {@link requestFinishActivityLocked} returns {@code true},
- // {@link adjustFocusedActivityStackLocked} would have been already called.
+ // {@link adjustFocusedActivityStack} would have been already called.
r.resumeKeyDispatchingLocked();
return;
}
@@ -3420,7 +3209,7 @@
}
if (r.app != null && r.app.thread != null) {
- adjustFocusedActivityStackLocked(r, "stopActivity");
+ adjustFocusedActivityStack(r, "stopActivity");
r.resumeKeyDispatchingLocked();
try {
r.stopped = false;
@@ -3659,7 +3448,7 @@
r.pauseKeyDispatchingLocked();
- adjustFocusedActivityStackLocked(r, "finishActivity");
+ adjustFocusedActivityStack(r, "finishActivity");
finishActivityResultsLocked(r, resultCode, resultData);
@@ -3685,7 +3474,7 @@
}
if (endTask) {
- mService.mLockTaskController.removeLockedTask(task);
+ mService.mLockTaskController.clearLockedTask(task);
}
} else if (r.state != ActivityState.PAUSING) {
// If the activity is PAUSING, we will complete the finish once
@@ -3826,7 +3615,21 @@
}
}
- final boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) {
+ /** @return true if the stack behind this one is a standard activity type. */
+ boolean inFrontOfStandardStack() {
+ final ActivityDisplay display = getDisplay();
+ if (display == null) {
+ return false;
+ }
+ final int index = display.getIndexOf(this);
+ if (index == 0) {
+ return false;
+ }
+ final ActivityStack stackBehind = display.getChildAt(index - 1);
+ return stackBehind.isActivityTypeStandard();
+ }
+
+ boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) {
// Basic case: for simple app-centric recents, we need to recreate
// the task if the affinity has changed.
if (srec == null || srec.getTask().affinity == null ||
@@ -3838,10 +3641,9 @@
// of a document, unless simply finishing it will return them to the the
// correct app behind.
final TaskRecord task = srec.getTask();
- if (srec.frontOfTask && task != null && task.getBaseIntent() != null
- && task.getBaseIntent().isDocument()) {
+ if (srec.frontOfTask && task.getBaseIntent() != null && task.getBaseIntent().isDocument()) {
// Okay, this activity is at the root of its task. What to do, what to do...
- if (!task.returnsToStandardTask()) {
+ if (!inFrontOfStandardStack()) {
// Finishing won't return to an application, so we need to recreate.
return true;
}
@@ -4041,11 +3843,6 @@
+ " onlyHasTaskOverlays=" + onlyHasTaskOverlays);
}
- if (mStackSupervisor.isFocusedStack(this) && task == topTask() &&
- task.isOverHomeStack()) {
- mStackSupervisor.moveHomeStackTaskToTop(reason);
- }
-
// The following block can be executed multiple times if there is more than one overlay.
// {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup
// of the task by id and exiting early if not found.
@@ -4537,8 +4334,9 @@
}
Slog.i(TAG, "moveTaskToBack: " + tr);
- // If the task is locked, then show the lock task toast
- if (mService.mLockTaskController.checkLockedTask(tr)) {
+ // In LockTask mode, moving a locked task to the back of the stack may expose unlocked
+ // ones. Therefore we need to check if this operation is allowed.
+ if (!mService.mLockTaskController.canMoveTaskToBack(tr)) {
return false;
}
@@ -4567,60 +4365,19 @@
if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task=" + taskId);
- boolean prevIsHome = false;
+ mTaskHistory.remove(tr);
+ mTaskHistory.add(0, tr);
+ updateTaskMovement(tr, false);
- // If true, we should resume the home activity next if the task we are moving to the
- // back is over the home stack. We force to false if the task we are moving to back
- // is the home task and we don't want it resumed after moving to the back.
- final boolean canGoHome = !tr.isActivityTypeHome() && tr.isOverHomeStack();
- if (canGoHome) {
- final TaskRecord nextTask = getNextTask(tr);
- if (nextTask != null) {
- nextTask.setTaskToReturnTo(tr.getTaskToReturnTo());
- } else {
- prevIsHome = true;
- }
- }
-
- boolean requiresMove = mTaskHistory.indexOf(tr) != 0;
- if (requiresMove) {
- mTaskHistory.remove(tr);
- mTaskHistory.add(0, tr);
- updateTaskMovement(tr, false);
-
- mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
- mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController());
- }
+ mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
+ mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController(),
+ true /* includingParents */);
if (inPinnedWindowingMode()) {
mStackSupervisor.removeStack(this);
return true;
}
- // Otherwise, there is an assumption that moving a task to the back moves it behind the
- // home activity. We make sure here that some activity in the stack will launch home.
- int numTasks = mTaskHistory.size();
- for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) {
- final TaskRecord task = mTaskHistory.get(taskNdx);
- if (task.isOverHomeStack()) {
- break;
- }
- if (taskNdx == 1) {
- // Set the last task before tr to go to home.
- task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
- }
- }
-
- final TaskRecord task = mResumedActivity != null ? mResumedActivity.getTask() : null;
- if (prevIsHome || (task == tr && canGoHome) || (numTasks <= 1 && isOnHomeDisplay())) {
- if (!mService.mBooting && !mService.mBooted) {
- // Not ready yet!
- return false;
- }
- tr.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
- return mStackSupervisor.resumeHomeStackTask(null, "moveTaskToBack");
- }
-
mStackSupervisor.resumeFocusedStackTopActivityLocked();
return true;
}
@@ -4851,69 +4608,43 @@
return didSomething;
}
- void getTasksLocked(List<RunningTaskInfo> list, int callingUid, boolean allowed) {
+ /**
+ * @return The set of running tasks through {@param tasksOut} that are available to the caller.
+ * If {@param ignoreActivityType} or {@param ignoreWindowingMode} are not undefined,
+ * then skip running tasks that match those types.
+ */
+ void getRunningTasks(List<TaskRecord> tasksOut, @ActivityType int ignoreActivityType,
+ @WindowingMode int ignoreWindowingMode, int callingUid, boolean allowed) {
boolean focusedStack = mStackSupervisor.getFocusedStack() == this;
boolean topTask = true;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
if (task.getTopActivity() == null) {
+ // Skip if there are no activities in the task
continue;
}
- ActivityRecord r = null;
- ActivityRecord top = null;
- ActivityRecord tmp;
- int numActivities = 0;
- int numRunning = 0;
- final ArrayList<ActivityRecord> activities = task.mActivities;
if (!allowed && !task.isActivityTypeHome() && task.effectiveUid != callingUid) {
+ // Skip if the caller can't fetch this task
continue;
}
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- tmp = activities.get(activityNdx);
- if (tmp.finishing) {
- continue;
- }
- r = tmp;
-
- // Initialize state for next task if needed.
- if (top == null || (top.state == ActivityState.INITIALIZING)) {
- top = r;
- numActivities = numRunning = 0;
- }
-
- // Add 'r' into the current task.
- numActivities++;
- if (r.app != null && r.app.thread != null) {
- numRunning++;
- }
-
- if (DEBUG_ALL) Slog.v(
- TAG, r.intent.getComponent().flattenToShortString()
- + ": task=" + r.getTask());
+ if (ignoreActivityType != ACTIVITY_TYPE_UNDEFINED
+ && task.getActivityType() == ignoreActivityType) {
+ // Skip ignored activity type
+ continue;
}
-
- RunningTaskInfo ci = new RunningTaskInfo();
- ci.id = task.taskId;
- ci.stackId = mStackId;
- ci.baseActivity = r.intent.getComponent();
- ci.topActivity = top.intent.getComponent();
- ci.lastActiveTime = task.lastActiveTime;
+ if (ignoreWindowingMode != WINDOWING_MODE_UNDEFINED
+ && task.getWindowingMode() == ignoreWindowingMode) {
+ // Skip ignored windowing mode
+ continue;
+ }
if (focusedStack && topTask) {
- // Give the latest time to ensure foreground task can be sorted
- // at the first, because lastActiveTime of creating task is 0.
- ci.lastActiveTime = SystemClock.elapsedRealtime();
+ // For the focused stack top task, update the last stack active time so that it can
+ // be used to determine the order of the tasks (it may not be set for newly created
+ // tasks)
+ task.lastActiveTime = SystemClock.elapsedRealtime();
topTask = false;
}
-
- if (top.getTask() != null) {
- ci.description = top.getTask().lastDescription;
- }
- ci.numActivities = numActivities;
- ci.numRunning = numRunning;
- ci.supportsSplitScreenMultiWindow = task.supportsSplitScreenWindowingMode();
- ci.resizeMode = task.mResizeMode;
- ci.configuration.setTo(task.getConfiguration());
- list.add(ci);
+ tasksOut.add(task);
}
}
@@ -5057,14 +4788,6 @@
onActivityRemovedFromStack(record);
}
- final int taskNdx = mTaskHistory.indexOf(task);
- final int topTaskNdx = mTaskHistory.size() - 1;
- if (task.isOverHomeStack() && taskNdx < topTaskNdx) {
- final TaskRecord nextTask = mTaskHistory.get(taskNdx + 1);
- if (!nextTask.isOverHomeStack() && !nextTask.isOverAssistantStack()) {
- nextTask.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
- }
- }
mTaskHistory.remove(task);
removeActivitiesFromLRUListLocked(task);
updateTaskMovement(task, true);
@@ -5094,7 +4817,7 @@
if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP
&& mStackSupervisor.isFocusedStack(this)) {
String myReason = reason + " leftTaskHistoryEmpty";
- if (mFullscreen || !adjustFocusToNextFocusableStackLocked(myReason)) {
+ if (mFullscreen || !adjustFocusToNextFocusableStack(myReason)) {
mStackSupervisor.moveHomeStackToFront(myReason);
}
}
@@ -5123,24 +4846,14 @@
addTask(task, toTop, "createTaskRecord");
final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController
.isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
- if (!layoutTaskInStack(task, info.windowLayout) && mBounds != null && task.isResizeable()
- && !isLockscreenShown) {
+ if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout)
+ && mBounds != null && task.isResizeable() && !isLockscreenShown) {
task.updateOverrideConfiguration(mBounds);
}
task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
return task;
}
- boolean layoutTaskInStack(TaskRecord task, ActivityInfo.WindowLayout windowLayout) {
- if (!task.inFreeformWindowingMode()) {
- return false;
- }
- mStackSupervisor.getLaunchingTaskPositioner()
- .updateDefaultBounds(task, mTaskHistory, windowLayout);
-
- return true;
- }
-
ArrayList<TaskRecord> getAllTasks() {
return new ArrayList<>(mTaskHistory);
}
@@ -5167,10 +4880,6 @@
mTaskHistory.add(position, task);
task.setStack(this);
- if (toTop) {
- updateTaskReturnToForTopInsertion(task);
- }
-
updateTaskMovement(task, toTop);
postAddTask(task, prevStack, schedulePictureInPictureModeChange);
@@ -5263,7 +4972,8 @@
public String toString() {
return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
+ " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType())
- + " mode=" + windowingModeToString(getWindowingMode()) + ", "
+ + " mode=" + windowingModeToString(getWindowingMode())
+ + " visible=" + shouldBeVisible(null /* starting */) + ", "
+ mTaskHistory.size() + " tasks}";
}
@@ -5290,7 +5000,7 @@
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- super.writeToProto(proto, CONFIGURATION_CONTAINER);
+ super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
proto.write(ID, mStackId);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c15b5e2..43a2a9f 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.START_ANY_ACTIVITY;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
@@ -83,6 +84,7 @@
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
@@ -110,7 +112,8 @@
import android.app.ProfilerInfo;
import android.app.ResultInfo;
import android.app.WaitResult;
-import android.app.WindowConfiguration;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -223,13 +226,6 @@
// at the top of its container (e.g. stack).
static final boolean ON_TOP = true;
- // Used to indicate that an objects (e.g. task) removal from its container
- // (e.g. stack) is due to it moving to another container.
- static final boolean MOVING = true;
-
- // Force the focus to change to the stack we are moving a task to..
- static final boolean FORCE_FOCUS = true;
-
// Don't execute any calls to resume.
static final boolean DEFER_RESUME = true;
@@ -287,15 +283,19 @@
final ActivityManagerService mService;
+ /** The historial list of recent tasks including inactive tasks */
RecentTasks mRecentTasks;
+ /** Helper class to abstract out logic for fetching the set of currently running tasks */
+ private RunningTasks mRunningTasks;
+
final ActivityStackSupervisorHandler mHandler;
/** Short cut */
WindowManagerService mWindowManager;
DisplayManager mDisplayManager;
- LaunchingTaskPositioner mTaskPositioner = new LaunchingTaskPositioner();
+ private final LaunchingBoundsController mLaunchingBoundsController;
/** Counter for next free stack ID to use for dynamic activity stacks. */
private int mNextFreeStackId = 0;
@@ -405,6 +405,7 @@
* object each time.
*/
private final Rect tempRect = new Rect();
+ private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic();
// The default minimal size that will be used if the activity doesn't specify its minimal size.
// It will be calculated when the default display gets added.
@@ -573,8 +574,12 @@
public ActivityStackSupervisor(ActivityManagerService service, Looper looper) {
mService = service;
mHandler = new ActivityStackSupervisorHandler(looper);
+ mRunningTasks = createRunningTasks();
mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
mKeyguardController = new KeyguardController(service, this);
+
+ mLaunchingBoundsController = new LaunchingBoundsController();
+ mLaunchingBoundsController.registerDefaultPositioners(this);
}
void setRecentTasks(RecentTasks recentTasks) {
@@ -582,6 +587,11 @@
mRecentTasks.registerCallback(this);
}
+ @VisibleForTesting
+ RunningTasks createRunningTasks() {
+ return new RunningTasks();
+ }
+
/**
* At the time when the constructor runs, the power manager has not yet been
* initialized. So we initialize our wakelocks afterwards.
@@ -685,10 +695,6 @@
return false;
}
- if (prev != null) {
- prev.getTask().setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
- }
-
mHomeStack.moveHomeStackTaskToTop();
ActivityRecord r = getHomeActivity();
final String myReason = reason + " resumeHomeStackTask";
@@ -1160,43 +1166,12 @@
return null;
}
- void getTasksLocked(int maxNum, List<RunningTaskInfo> list, int callingUid, boolean allowed) {
- // Gather all of the running tasks for each stack into runningTaskLists.
- ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists = new ArrayList<>();
- final int numDisplays = mActivityDisplays.size();
- for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
- final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
- for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = display.getChildAt(stackNdx);
- ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<>();
- runningTaskLists.add(stackTaskList);
- stack.getTasksLocked(stackTaskList, callingUid, allowed);
- }
- }
-
- // The lists are already sorted from most recent to oldest. Just pull the most recent off
- // each list and add it to list. Stop when all lists are empty or maxNum reached.
- while (maxNum > 0) {
- long mostRecentActiveTime = Long.MIN_VALUE;
- ArrayList<RunningTaskInfo> selectedStackList = null;
- final int numTaskLists = runningTaskLists.size();
- for (int stackNdx = 0; stackNdx < numTaskLists; ++stackNdx) {
- ArrayList<RunningTaskInfo> stackTaskList = runningTaskLists.get(stackNdx);
- if (!stackTaskList.isEmpty()) {
- final long lastActiveTime = stackTaskList.get(0).lastActiveTime;
- if (lastActiveTime > mostRecentActiveTime) {
- mostRecentActiveTime = lastActiveTime;
- selectedStackList = stackTaskList;
- }
- }
- }
- if (selectedStackList != null) {
- list.add(selectedStackList.remove(0));
- --maxNum;
- } else {
- break;
- }
- }
+ @VisibleForTesting
+ void getRunningTasks(int maxNum, List<RunningTaskInfo> list,
+ @ActivityType int ignoreActivityType, @WindowingMode int ignoreWindowingMode,
+ int callingUid, boolean allowed) {
+ mRunningTasks.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode,
+ mActivityDisplays, callingUid, allowed);
}
ActivityInfo resolveActivity(Intent intent, ResolveInfo rInfo, int startFlags,
@@ -1333,8 +1308,11 @@
mService.updateLruProcessLocked(app, true, null);
mService.updateOomAdjLocked();
- if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE ||
- task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV) {
+ if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+ || task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV
+ || (task.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED
+ && mService.mLockTaskController.getLockTaskModeState()
+ == LOCK_TASK_MODE_LOCKED)) {
mService.mLockTaskController.startLockTaskMode(task, false, 0 /* blank UID */);
}
@@ -1592,7 +1570,10 @@
return false;
}
if (options != null) {
- if (options.getLaunchTaskId() != INVALID_STACK_ID) {
+ // If a launch task id is specified, then ensure that the caller is the recents
+ // component or has the START_TASKS_FROM_RECENTS permission
+ if (options.getLaunchTaskId() != INVALID_TASK_ID
+ && !mRecentTasks.isCallerRecents(callingUid)) {
final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS,
callingPid, callingUid);
if (startInTaskPerm == PERMISSION_DENIED) {
@@ -2097,21 +2078,26 @@
}
}
- void findTaskToMoveToFrontLocked(TaskRecord task, int flags, ActivityOptions options,
+ void findTaskToMoveToFront(TaskRecord task, int flags, ActivityOptions options,
String reason, boolean forceNonResizeable) {
+ final ActivityStack currentStack = task.getStack();
+ if (currentStack == null) {
+ Slog.e(TAG, "findTaskToMoveToFront: can't move task="
+ + task + " to front. Stack is null");
+ return;
+ }
+
if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
mUserLeaving = true;
}
- if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
- // Caller wants the home activity moved with it. To accomplish this,
- // we'll just indicate that this task returns to the home task.
- task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
- }
- final ActivityStack currentStack = task.getStack();
- if (currentStack == null) {
- Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task="
- + task + " to front. Stack is null");
- return;
+
+ final ActivityRecord prev = topRunningActivityLocked();
+
+ if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0
+ || (prev != null && prev.isActivityTypeRecents())) {
+ // Caller wants the home activity moved with it or the previous task is recents in which
+ // case we always return home from the task we are moving to the front.
+ moveHomeStackToFront("findTaskToMoveToFront");
}
if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
@@ -2122,7 +2108,7 @@
if (stack != currentStack) {
task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE, DEFER_RESUME,
- "findTaskToMoveToFrontLocked");
+ "findTaskToMoveToFront");
stack = currentStack;
// moveTaskToStackUncheckedLocked() should already placed the task on top,
// still need moveTaskToFrontLocked() below for any transition settings.
@@ -2161,8 +2147,8 @@
|| mService.mSupportsFreeformWindowManagement;
}
- LaunchingTaskPositioner getLaunchingTaskPositioner() {
- return mTaskPositioner;
+ LaunchingBoundsController getLaunchingBoundsController() {
+ return mLaunchingBoundsController;
}
protected <T extends ActivityStack> T getStack(int stackId) {
@@ -2186,89 +2172,6 @@
return null;
}
- /**
- * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
- * @param windowingMode The windowing mode we are checking support for.
- * @param supportsMultiWindow If we should consider support for multi-window mode in general.
- * @param supportsSplitScreen If we should consider support for split-screen multi-window.
- * @param supportsFreeform If we should consider support for freeform multi-window.
- * @param supportsPip If we should consider support for picture-in-picture mutli-window.
- * @param activityType The activity type under consideration.
- * @return true if the windowing mode is supported.
- */
- boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
- boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
- int activityType) {
-
- if (windowingMode == WINDOWING_MODE_UNDEFINED
- || windowingMode == WINDOWING_MODE_FULLSCREEN) {
- return true;
- }
- if (!supportsMultiWindow) {
- return false;
- }
-
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
- return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
- windowingMode, activityType);
- }
-
- if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
- return false;
- }
-
- if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
- return false;
- }
- return true;
- }
-
- private int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
- @Nullable TaskRecord task, int activityType) {
-
- // First preference if the windowing mode in the activity options if set.
- int windowingMode = (options != null)
- ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
-
- // If windowing mode is unset, then next preference is the candidate task, then the
- // activity record.
- if (windowingMode == WINDOWING_MODE_UNDEFINED) {
- if (task != null) {
- windowingMode = task.getWindowingMode();
- }
- if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
- windowingMode = r.getWindowingMode();
- }
- }
-
- // Make sure the windowing mode we are trying to use makes sense for what is supported.
- boolean supportsMultiWindow = mService.mSupportsMultiWindow;
- boolean supportsSplitScreen = mService.mSupportsSplitScreenMultiWindow;
- boolean supportsFreeform = mService.mSupportsFreeformWindowManagement;
- boolean supportsPip = mService.mSupportsPictureInPicture;
- if (supportsMultiWindow) {
- if (task != null) {
- supportsMultiWindow = task.isResizeable();
- supportsSplitScreen = task.supportsSplitScreenWindowingMode();
- // TODO: Do we need to check for freeform and Pip support here?
- } else if (r != null) {
- supportsMultiWindow = r.isResizeable();
- supportsSplitScreen = r.supportsSplitScreenWindowingMode();
- supportsFreeform = r.supportsFreeform();
- supportsPip = r.supportsPictureInPicture();
- }
- }
-
- if (windowingMode != WINDOWING_MODE_UNDEFINED
- && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
- supportsFreeform, supportsPip, activityType)) {
- return windowingMode;
- }
- // Return root/systems windowing mode
- return getWindowingMode();
- }
-
int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
@Nullable TaskRecord task) {
// Preference is given to the activity type for the activity then the task since the type
@@ -2329,7 +2232,6 @@
}
final int activityType = resolveActivityType(r, options, candidateTask);
- int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
T stack = null;
// Next preference for stack goes to the display Id set in the activity options or the
@@ -2347,7 +2249,7 @@
}
final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
if (display != null) {
- stack = display.getOrCreateStack(windowingMode, activityType, onTop);
+ stack = display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
if (stack != null) {
return stack;
}
@@ -2365,10 +2267,14 @@
stack = r.getStack();
}
if (stack != null) {
- if (stack.isCompatible(windowingMode, activityType)) {
- return stack;
- }
display = stack.getDisplay();
+ if (display != null) {
+ final int windowingMode =
+ display.resolveWindowingMode(r, options, candidateTask, activityType);
+ if (stack.isCompatible(windowingMode, activityType)) {
+ return stack;
+ }
+ }
}
if (display == null
@@ -2379,7 +2285,7 @@
display = getDefaultDisplay();
}
- return display.getOrCreateStack(windowingMode, activityType, onTop);
+ return display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
}
/**
@@ -2596,6 +2502,8 @@
final ActivityDisplay toDisplay = getActivityDisplay(toDisplayId);
if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ // Tell the display we are exiting split-screen mode.
+ toDisplay.onExitingSplitScreenMode();
// We are moving all tasks from the docked stack to the fullscreen stack,
// which is dismissing the docked stack, so resize all other stacks to
// fullscreen here already so we don't end up with resize trashing.
@@ -2625,35 +2533,27 @@
final ArrayList<TaskRecord> tasks = fromStack.getAllTasks();
if (!tasks.isEmpty()) {
+ mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
final int size = tasks.size();
- final ActivityStack fullscreenStack = toDisplay.getOrCreateStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, onTop);
+ for (int i = 0; i < size; ++i) {
+ final TaskRecord task = tasks.get(i);
+ final ActivityStack toStack = toDisplay.getOrCreateStack(
+ null, mTmpOptions, task, task.getActivityType(), onTop);
- if (onTop) {
- final int returnToType =
- toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
- for (int i = 0; i < size; i++) {
- final TaskRecord task = tasks.get(i);
+ if (onTop) {
+ final int returnToType =
+ toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
final boolean isTopTask = i == (size - 1);
- if (inPinnedWindowingMode) {
- // Update the return-to to reflect where the pinned stack task was moved
- // from so that we retain the stack that was previously visible if the
- // pinned stack is recreated. See moveActivityToPinnedStackLocked().
- task.setTaskToReturnTo(returnToType);
- }
// Defer resume until all the tasks have been moved to the fullscreen stack
- task.reparent(fullscreenStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
+ task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
isTopTask /* animate */, DEFER_RESUME,
schedulePictureInPictureModeChange,
"moveTasksToFullscreenStack - onTop");
- }
- } else {
- for (int i = 0; i < size; i++) {
- final TaskRecord task = tasks.get(i);
+ } else {
// Position the tasks in the fullscreen stack in order at the bottom of the
// stack. Also defer resume until all the tasks have been moved to the
// fullscreen stack.
- task.reparent(fullscreenStack, i /* position */,
+ task.reparent(toStack, ON_TOP,
REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
schedulePictureInPictureModeChange,
"moveTasksToFullscreenStack - NOT_onTop");
@@ -2874,6 +2774,7 @@
if (tr != null) {
tr.removeTaskActivitiesLocked(pauseImmediately);
cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
+ mService.mLockTaskController.clearLockedTask(tr);
if (tr.isPersistable) {
mService.notifyTaskPersisterLocked(null, true);
}
@@ -3080,23 +2981,16 @@
+ " task=" + task);
}
- // We don't allow moving a unresizeable task to the docked stack since the docked stack is
- // used for split-screen mode and will cause things like the docked divider to show up. We
- // instead leave the task in its current stack or move it to the fullscreen stack if it
- // isn't currently in a stack.
+ // Leave the task in its current stack or a fullscreen stack if it isn't resizeable and the
+ // preferred stack is in multi-window mode.
if (inMultiWindowMode && !task.isResizeable()) {
- Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack."
- + " Moving to stackId=" + stackId + " instead.");
- // Temporarily disable resizeablility of the task as we don't want it to be resized if,
- // for example, a docked stack is created which will lead to the stack we are moving
- // from being resized and and its resizeable tasks being resized.
- try {
- task.mTemporarilyUnresizable = true;
- stack = stack.getDisplay().createStack(
- WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
- } finally {
- task.mTemporarilyUnresizable = false;
+ Slog.w(TAG, "Can not move unresizeable task=" + task + " to multi-window stack=" + stack
+ + " Moving to a fullscreen stack instead.");
+ if (prevStack != null) {
+ return prevStack;
}
+ stack = stack.getDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
}
return stack;
}
@@ -3123,12 +3017,12 @@
}
moveActivityToPinnedStackLocked(r, null /* sourceBounds */, 0f /* aspectRatio */,
- true /* moveHomeStackToFront */, "moveTopActivityToPinnedStack");
+ "moveTopActivityToPinnedStack");
return true;
}
void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceHintBounds, float aspectRatio,
- boolean moveHomeStackToFront, String reason) {
+ String reason) {
mWindowManager.deferSurfaceLayout();
@@ -3154,17 +3048,6 @@
true /* allowResizeInDockedMode */, !DEFER_RESUME);
if (task.mActivities.size() == 1) {
- // There is only one activity in the task. So, we can just move the task over to
- // the stack without re-parenting the activity in a different task. We don't
- // move the home stack forward if we are currently entering picture-in-picture
- // while pausing because that changes the focused stack and may prevent the new
- // starting activity from resuming.
- if (moveHomeStackToFront && task.returnsToHomeTask()
- && (r.state == RESUMED || !r.supportsEnterPipOnTaskSwitch)) {
- // Move the home stack forward if the task we just moved to the pinned stack
- // was launched from home so home should be visible behind it.
- moveHomeStackToFront(reason);
- }
// Defer resume until below, and do not schedule PiP changes until we animate below
task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME,
false /* schedulePictureInPictureModeChange */, reason);
@@ -3799,7 +3682,7 @@
}
public void writeToProto(ProtoOutputStream proto) {
- super.writeToProto(proto, CONFIGURATION_CONTAINER);
+ super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
activityDisplay.writeToProto(proto, DISPLAYS);
@@ -4287,9 +4170,9 @@
final boolean isSecondaryDisplayPreferred =
(preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
final boolean inSplitScreenMode = actualStack != null
- && actualStack.inSplitScreenWindowingMode();
+ && actualStack.getDisplay().hasSplitScreenPrimaryStack();
if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
- && !isSecondaryDisplayPreferred) || task.isActivityTypeHome()) {
+ && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) {
return;
}
@@ -4588,7 +4471,7 @@
try {
if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
mWindowManager.setDockedStackCreateState(
- activityOptions.getDockCreateMode(), null /* initialBounds */);
+ activityOptions.getSplitScreenCreateMode(), null /* initialBounds */);
// Defer updating the stack in which recents is until the app transition is done, to
// not run into issues where we still need to draw the task in recents but the
@@ -4606,6 +4489,10 @@
"startActivityFromRecents: Task " + taskId + " not found.");
}
+ // We always want to return to the home activity instead of the recents activity from
+ // whatever is started from the recents activity, so move the home stack forward.
+ moveHomeStackToFront("startActivityFromRecents");
+
// If the user must confirm credentials (e.g. when first launching a work app and the
// Work Challenge is present) let startActivityInPackage handle the intercepting.
if (!mService.mUserController.shouldConfirmCredentials(task.userId)
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 6f74d85..1c80282 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -26,9 +26,6 @@
import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -151,7 +148,7 @@
private boolean mLaunchTaskBehind;
private int mLaunchFlags;
- private Rect mLaunchBounds;
+ private Rect mLaunchBounds = new Rect();
private ActivityRecord mNotTop;
private boolean mDoResume;
@@ -169,9 +166,6 @@
private Intent mNewTaskIntent;
private ActivityStack mSourceStack;
private ActivityStack mTargetStack;
- // Indicates that we moved other task and are going to put something on top soon, so
- // we don't want to show it redundantly or accidentally change what's shown below.
- private boolean mMovedOtherTask;
private boolean mMovedToFront;
private boolean mNoAnimation;
private boolean mKeepCurTransition;
@@ -210,7 +204,7 @@
mLaunchFlags = 0;
mLaunchMode = INVALID_LAUNCH_MODE;
- mLaunchBounds = null;
+ mLaunchBounds.setEmpty();
mNotTop = null;
mDoResume = false;
@@ -227,7 +221,6 @@
mSourceStack = null;
mTargetStack = null;
- mMovedOtherTask = false;
mMovedToFront = false;
mNoAnimation = false;
mKeepCurTransition = false;
@@ -1184,12 +1177,8 @@
mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.userId);
mService.grantEphemeralAccessLocked(mStartActivity.userId, mIntent,
mStartActivity.appInfo.uid, UserHandle.getAppId(mCallingUid));
- if (mSourceRecord != null) {
- mStartActivity.getTask().setTaskToReturnTo(mSourceRecord);
- }
if (newTask) {
- EventLog.writeEvent(
- EventLogTags.AM_CREATE_TASK, mStartActivity.userId,
+ EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.userId,
mStartActivity.getTask().taskId);
}
ActivityStack.logStartActivity(
@@ -1254,7 +1243,10 @@
mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options);
- mLaunchBounds = getOverrideBounds(r, options, inTask);
+ mLaunchBounds.setEmpty();
+
+ mSupervisor.getLaunchingBoundsController().calculateBounds(inTask, null /*layout*/, r,
+ sourceRecord, options, mLaunchBounds);
mLaunchMode = r.launchMode;
@@ -1579,7 +1571,6 @@
if (mLaunchTaskBehind && mSourceRecord != null) {
intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
}
- mMovedOtherTask = true;
// If the launch flags carry both NEW_TASK and CLEAR_TASK, the task's activities
// will be cleared soon by ActivityStarter in setTaskFromIntentActivity().
@@ -1644,7 +1635,6 @@
intentActivity.showStartingWindow(null /* prev */, false /* newTask */,
true /* taskSwitch */);
}
- updateTaskReturnToType(intentActivity.getTask(), mLaunchFlags, focusStack);
}
}
if (!mMovedToFront && mDoResume) {
@@ -1663,27 +1653,6 @@
return intentActivity;
}
- private void updateTaskReturnToType(
- TaskRecord task, int launchFlags, ActivityStack focusedStack) {
- if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
- == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
- // Caller wants to appear on home activity.
- task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
- return;
- } else if (focusedStack == null || focusedStack.isActivityTypeHome()) {
- // Task will be launched over the home stack, so return home.
- task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
- return;
- } else if (focusedStack != task.getStack() && focusedStack.isActivityTypeAssistant()) {
- // Task was launched over the assistant stack, so return there
- task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
- return;
- }
-
- // Else we are coming from an application stack so return to an application.
- task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
- }
-
private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
@@ -1700,11 +1669,6 @@
task.performClearTaskLocked();
mReuseTask = task;
mReuseTask.setIntent(mStartActivity);
-
- // When we clear the task - focus will be adjusted, which will bring another task
- // to top before we launch the activity we need. This will temporary swap their
- // mTaskToReturnTo values and we don't want to overwrite them accidentally.
- mMovedOtherTask = true;
} else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity,
@@ -1725,7 +1689,7 @@
// Target stack got cleared when we all activities were removed above.
// Go ahead and reset it.
mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
- null /* bounds */, mLaunchFlags, mOptions);
+ mLaunchFlags, mOptions);
mTargetStack.addTask(task,
!mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
}
@@ -1776,8 +1740,7 @@
private int setTaskFromReuseOrCreateNewTask(
TaskRecord taskToAffiliate, ActivityStack topStack) {
- mTargetStack = computeStackFocus(
- mStartActivity, true, mLaunchBounds, mLaunchFlags, mOptions);
+ mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);
// Do no move the target stack to front yet, as we might bail if
// isLockTaskModeViolation fails below.
@@ -1806,15 +1769,6 @@
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
- if (!mMovedOtherTask) {
- // If stack id is specified in activity options, usually it means that activity is
- // launched not from currently focused stack (e.g. from SysUI or from shell) - in
- // that case we check the target stack.
- // TODO: Not sure I understand the value or use of the commented out code and the
- // comment above. See if this causes any issues and why...
- updateTaskReturnToType(mStartActivity.getTask(), mLaunchFlags,
- /*preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : */topStack);
- }
if (mDoResume) {
mTargetStack.moveToFront("reuseOrNewTask");
}
@@ -1962,7 +1916,7 @@
return START_TASK_TO_FRONT;
}
- if (mLaunchBounds != null) {
+ if (!mLaunchBounds.isEmpty()) {
// TODO: Shouldn't we already know what stack to use by the time we get here?
ActivityStack stack = mSupervisor.getLaunchStack(null, null, mInTask, ON_TOP);
if (stack != mInTask.getStack()) {
@@ -1985,7 +1939,7 @@
}
void updateBounds(TaskRecord task, Rect bounds) {
- if (bounds == null) {
+ if (bounds.isEmpty()) {
return;
}
@@ -1998,8 +1952,7 @@
}
private void setTaskToCurrentTopOrCreateNewTask() {
- mTargetStack = computeStackFocus(mStartActivity, false, null /* bounds */, mLaunchFlags,
- mOptions);
+ mTargetStack = computeStackFocus(mStartActivity, false, mLaunchFlags, mOptions);
if (mDoResume) {
mTargetStack.moveToFront("addingToTopTask");
}
@@ -2062,8 +2015,8 @@
}
}
- private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds,
- int launchFlags, ActivityOptions aOptions) {
+ private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, int launchFlags,
+ ActivityOptions aOptions) {
final TaskRecord task = r.getTask();
ActivityStack stack = getLaunchStack(r, launchFlags, task, aOptions);
if (stack != null) {
@@ -2214,15 +2167,6 @@
}
}
- private Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
- Rect newBounds = null;
- if (mSupervisor.canUseActivityOptionsLaunchBounds(options)
- && (r.isResizeable() || (inTask != null && inTask.isResizeable()))) {
- newBounds = TaskRecord.validateBounds(options.getLaunchBounds());
- }
- return newBounds;
- }
-
private boolean isLaunchModeOneOf(int mode1, int mode2) {
return mode1 == mLaunchMode || mode2 == mLaunchMode;
}
diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java
index d42a3b9..17626ea 100644
--- a/services/core/java/com/android/server/am/AppTaskImpl.java
+++ b/services/core/java/com/android/server/am/AppTaskImpl.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
import android.app.ActivityManager;
@@ -76,11 +77,12 @@
synchronized (mService) {
long origId = Binder.clearCallingIdentity();
try {
- TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId);
+ TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId,
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (tr == null) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
- return RecentTasks.createRecentTaskInfo(tr);
+ return mService.getRecentTasks().createRecentTaskInfo(tr);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -110,7 +112,8 @@
TaskRecord tr;
IApplicationThread appThread;
synchronized (mService) {
- tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId);
+ tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId,
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (tr == null) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
@@ -131,7 +134,8 @@
synchronized (mService) {
long origId = Binder.clearCallingIdentity();
try {
- TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId);
+ TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId,
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (tr == null) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
diff --git a/services/core/java/com/android/server/am/AssistDataReceiverProxy.java b/services/core/java/com/android/server/am/AssistDataReceiverProxy.java
new file mode 100644
index 0000000..8306731
--- /dev/null
+++ b/services/core/java/com/android/server/am/AssistDataReceiverProxy.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.graphics.Bitmap;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.app.IAssistDataReceiver;
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
+
+/**
+ * Proxies assist data to the given receiver, skipping all callbacks if the receiver dies.
+ */
+class AssistDataReceiverProxy implements AssistDataRequesterCallbacks,
+ Binder.DeathRecipient {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "AssistDataReceiverProxy" : TAG_AM;
+
+ private String mCallerPackage;
+ private boolean mBinderDied;
+ private IAssistDataReceiver mReceiver;
+
+ public AssistDataReceiverProxy(IAssistDataReceiver receiver, String callerPackage) {
+ try {
+ receiver.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Could not link to client death", e);
+ }
+ mReceiver = receiver;
+ mCallerPackage = callerPackage;
+ }
+
+ @Override
+ public boolean canHandleReceivedAssistDataLocked() {
+ // We are forwarding, so we can always receive this data
+ return true;
+ }
+
+ @Override
+ public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+ if (!mBinderDied) {
+ try {
+ mReceiver.onHandleAssistData(data);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to proxy assist data to receiver in package="
+ + mCallerPackage, e);
+ }
+ }
+ }
+
+ @Override
+ public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+ if (!mBinderDied) {
+ try {
+ mReceiver.onHandleAssistScreenshot(screenshot);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to proxy assist screenshot to receiver in package="
+ + mCallerPackage, e);
+ }
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ mBinderDied = true;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
new file mode 100644
index 0000000..e32ff6e
--- /dev/null
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.ASSIST_CONTEXT_FULL;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_NONE;
+
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAssistDataReceiver;
+import com.android.internal.logging.MetricsLogger;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to asynchronously fetch the assist data and screenshot from the current running
+ * activities. It manages received data and calls back to the owner when the owner is ready to
+ * receive the data itself.
+ */
+public class AssistDataRequester extends IAssistDataReceiver.Stub {
+
+ public static final String KEY_RECEIVER_EXTRA_COUNT = "count";
+ public static final String KEY_RECEIVER_EXTRA_INDEX = "index";
+
+ private IActivityManager mService;
+ private IWindowManager mWindowManager;
+ private Context mContext;
+ private AppOpsManager mAppOpsManager;
+
+ private AssistDataRequesterCallbacks mCallbacks;
+ private Object mCallbacksLock;
+
+ private int mRequestStructureAppOps;
+ private int mRequestScreenshotAppOps;
+ private boolean mCanceled;
+ private int mPendingDataCount;
+ private int mPendingScreenshotCount;
+ private final ArrayList<Bundle> mAssistData = new ArrayList<>();
+ private final ArrayList<Bitmap> mAssistScreenshot = new ArrayList<>();
+
+
+ /**
+ * Interface to handle the events from the fetcher.
+ */
+ public interface AssistDataRequesterCallbacks {
+ /**
+ * @return whether the currently received assist data can be handled by the callbacks.
+ */
+ @GuardedBy("mCallbacksLock")
+ boolean canHandleReceivedAssistDataLocked();
+
+ /**
+ * Called when we receive asynchronous assist data. This call is only made if the
+ * {@param fetchData} argument to requestAssistData() is true, and if the current activity
+ * allows assist data to be fetched. In addition, the callback will be made with the
+ * {@param mCallbacksLock} held, and only if {@link #canHandleReceivedAssistDataLocked()}
+ * is true.
+ */
+ @GuardedBy("mCallbacksLock")
+ void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount);
+
+ /**
+ * Called when we receive asynchronous assist screenshot. This call is only made if
+ * {@param fetchScreenshot} argument to requestAssistData() is true, and if the current
+ * activity allows assist data to be fetched. In addition, the callback will be made with
+ * the {@param mCallbacksLock} held, and only if
+ * {@link #canHandleReceivedAssistDataLocked()} is true.
+ */
+ @GuardedBy("mCallbacksLock")
+ void onAssistScreenshotReceivedLocked(Bitmap screenshot);
+ }
+
+ /**
+ * @param callbacks The callbacks to handle the asynchronous reply with the assist data.
+ * @param callbacksLock The lock for the requester to hold when calling any of the
+ * {@param callbacks}. The owner should also take care in locking
+ * appropriately when calling into this requester.
+ * @param requestStructureAppOps The app ops to check before requesting the assist structure
+ * @param requestScreenshotAppOps The app ops to check before requesting the assist screenshot.
+ * This can be {@link AppOpsManager#OP_NONE} to indicate that
+ * screenshots should never be fetched.
+ */
+ public AssistDataRequester(Context context, IActivityManager service,
+ IWindowManager windowManager, AppOpsManager appOpsManager,
+ AssistDataRequesterCallbacks callbacks, Object callbacksLock,
+ int requestStructureAppOps, int requestScreenshotAppOps) {
+ mCallbacks = callbacks;
+ mCallbacksLock = callbacksLock;
+ mWindowManager = windowManager;
+ mService = service;
+ mContext = context;
+ mAppOpsManager = appOpsManager;
+ mRequestStructureAppOps = requestStructureAppOps;
+ mRequestScreenshotAppOps = requestScreenshotAppOps;
+ }
+
+ /**
+ * Request that assist data be loaded asynchronously. The resulting data will be provided
+ * through the {@link AssistDataRequesterCallbacks}.
+ *
+ * @param activityTokens the list of visible activities
+ * @param fetchData whether or not to fetch the assist data, only applies if the caller is
+ * allowed to fetch the assist data, and the current activity allows assist data to be
+ * fetched from it
+ * @param fetchScreenshot whether or not to fetch the screenshot, only applies if fetchData is
+ * true, the caller is allowed to fetch the assist data, and the current activity allows
+ * assist data to be fetched from it
+ */
+ public void requestAssistData(List<IBinder> activityTokens, boolean fetchData,
+ boolean fetchScreenshot, int callingUid, String callingPackage) {
+ // TODO: Better handle the cancel case if a request can be reused
+ // TODO: Known issue, if the assist data is not allowed on the current activity, then no
+ // assist data is requested for any of the other activities
+
+ // Early exit if there are no activity to fetch for
+ if (activityTokens.isEmpty()) {
+ return;
+ }
+
+ // Ensure that the current activity supports assist data
+ boolean isAssistDataAllowed = false;
+ try {
+ isAssistDataAllowed = mService.isAssistDataAllowedOnCurrentActivity();
+ } catch (RemoteException e) {
+ // Should never happen
+ }
+ fetchData &= isAssistDataAllowed;
+ fetchScreenshot &= fetchData && isAssistDataAllowed
+ && (mRequestScreenshotAppOps != OP_NONE);
+
+ mCanceled = false;
+ mPendingDataCount = 0;
+ mPendingScreenshotCount = 0;
+ mAssistData.clear();
+ mAssistScreenshot.clear();
+
+ if (fetchData) {
+ if (mAppOpsManager.checkOpNoThrow(mRequestStructureAppOps, callingUid, callingPackage)
+ == MODE_ALLOWED) {
+ final int numActivities = activityTokens.size();
+ for (int i = 0; i < numActivities; i++) {
+ IBinder topActivity = activityTokens.get(i);
+ try {
+ MetricsLogger.count(mContext, "assist_with_context", 1);
+ Bundle receiverExtras = new Bundle();
+ receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i);
+ receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, numActivities);
+ if (mService.requestAssistContextExtras(ASSIST_CONTEXT_FULL, this,
+ receiverExtras, topActivity, /* focused= */ i == 0,
+ /* newSessionId= */ i == 0)) {
+ mPendingDataCount++;
+ } else if (i == 0) {
+ // Wasn't allowed... given that, let's not do the screenshot either.
+ dispatchAssistDataReceived(null);
+ fetchScreenshot = false;
+ break;
+ }
+ } catch (RemoteException e) {
+ // Can't happen
+ }
+ }
+ } else {
+ // Wasn't allowed... given that, let's not do the screenshot either.
+ dispatchAssistDataReceived(null);
+ fetchScreenshot = false;
+ }
+ }
+
+ if (fetchScreenshot) {
+ if (mAppOpsManager.checkOpNoThrow(mRequestScreenshotAppOps, callingUid, callingPackage)
+ == MODE_ALLOWED) {
+ try {
+ MetricsLogger.count(mContext, "assist_with_screen", 1);
+ mPendingScreenshotCount++;
+ mWindowManager.requestAssistScreenshot(this);
+ } catch (RemoteException e) {
+ // Can't happen
+ }
+ } else {
+ dispatchAssistScreenshotReceived(null);
+ }
+ }
+ }
+
+ /**
+ * This call should only be made when the callbacks are capable of handling the received assist
+ * data.
+ */
+ public void processPendingAssistData() {
+ final int dataCount = mAssistData.size();
+ for (int i = 0; i < dataCount; i++) {
+ dispatchAssistDataReceived(mAssistData.get(i));
+ }
+ final int screenshotsCount = mAssistScreenshot.size();
+ for (int i = 0; i < screenshotsCount; i++) {
+ dispatchAssistScreenshotReceived(mAssistScreenshot.get(i));
+ }
+ }
+
+ public int getPendingDataCount() {
+ return mPendingDataCount;
+ }
+
+ public int getPendingScreenshotCount() {
+ return mPendingScreenshotCount;
+ }
+
+ /**
+ * Cancels the current request for the assist data.
+ */
+ public void cancel() {
+ // Reset the pending data count, if we receive new assist data after this point, it will
+ // be ignored
+ mCanceled = true;
+ mPendingDataCount = 0;
+ mPendingScreenshotCount = 0;
+ mAssistData.clear();
+ mAssistScreenshot.clear();
+ }
+
+ @Override
+ public void onHandleAssistData(Bundle data) {
+ synchronized (mCallbacksLock) {
+ if (mCanceled) {
+ return;
+ }
+ mPendingDataCount--;
+
+ if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+ // Process any pending data and dispatch the new data as well
+ processPendingAssistData();
+ dispatchAssistDataReceived(data);
+ } else {
+ // Queue up the data for processing later
+ mAssistData.add(data);
+ }
+ }
+ }
+
+ @Override
+ public void onHandleAssistScreenshot(Bitmap screenshot) {
+ synchronized (mCallbacksLock) {
+ if (mCanceled) {
+ return;
+ }
+ mPendingScreenshotCount--;
+
+ if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+ // Process any pending data and dispatch the new data as well
+ processPendingAssistData();
+ dispatchAssistScreenshotReceived(screenshot);
+ } else {
+ // Queue up the data for processing later
+ mAssistScreenshot.add(screenshot);
+ }
+ }
+ }
+
+ private void dispatchAssistDataReceived(Bundle data) {
+ int activityIndex = 0;
+ int activityCount = 0;
+ final Bundle receiverExtras = data != null
+ ? data.getBundle(ASSIST_KEY_RECEIVER_EXTRAS) : null;
+ if (receiverExtras != null) {
+ activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
+ activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
+ }
+ mCallbacks.onAssistDataReceivedLocked(data, activityIndex, activityCount);
+ }
+
+ private void dispatchAssistScreenshotReceived(Bitmap screenshot) {
+ mCallbacks.onAssistScreenshotReceivedLocked(screenshot);
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("mPendingDataCount="); pw.println(mPendingDataCount);
+ pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
+ pw.print(prefix); pw.print("mPendingScreenshotCount="); pw.println(mPendingScreenshotCount);
+ pw.print(prefix); pw.print("mAssistScreenshot="); pw.println(mAssistScreenshot);
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 68ed9ae..52f9702 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -23,6 +23,7 @@
import android.net.wifi.WifiActivityEnergyInfo;
import android.os.PowerSaveState;
import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -177,9 +178,22 @@
}
public void publish() {
+ LocalServices.addService(BatteryStatsInternal.class, new LocalService());
ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
}
+ private final class LocalService extends BatteryStatsInternal {
+ @Override
+ public String[] getWifiIfaces() {
+ return mStats.getWifiIfaces().clone();
+ }
+
+ @Override
+ public String[] getMobileIfaces() {
+ return mStats.getMobileIfaces().clone();
+ }
+ }
+
private static void awaitUninterruptibly(Future<?> future) {
while (true) {
try {
@@ -297,31 +311,39 @@
void noteProcessStart(String name, int uid) {
synchronized (mStats) {
mStats.noteProcessStartLocked(name, uid);
+ // TODO: decide where this should be and use a constant instead of a literal.
+ StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 1);
}
}
void noteProcessCrash(String name, int uid) {
synchronized (mStats) {
mStats.noteProcessCrashLocked(name, uid);
+ // TODO: decide where this should be and use a constant instead of a literal.
+ StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 2);
}
}
void noteProcessAnr(String name, int uid) {
synchronized (mStats) {
mStats.noteProcessAnrLocked(name, uid);
+ // TODO: decide where this should be and use a constant instead of a literal.
+ StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 3);
}
}
void noteProcessFinish(String name, int uid) {
synchronized (mStats) {
mStats.noteProcessFinishLocked(name, uid);
+ // TODO: decide where this should be and use a constant instead of a literal.
+ StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 0);
}
}
void noteUidProcessState(int uid, int state) {
synchronized (mStats) {
// TODO: remove this once we figure out properly where and how
- StatsLog.write(StatsLog.PROCESS_STATE_CHANGED, uid, state);
+ StatsLog.write(StatsLog.UID_PROCESS_STATE_CHANGED, uid, state);
mStats.noteUidProcessStateLocked(uid, state);
}
@@ -548,6 +570,7 @@
public void noteScreenBrightness(int brightness) {
enforceCallingPermission();
synchronized (mStats) {
+ StatsLog.write(StatsLog.SCREEN_BRIGHTNESS_CHANGED, brightness);
mStats.noteScreenBrightnessLocked(brightness);
}
}
@@ -909,6 +932,7 @@
enforceCallingPermission();
synchronized (mStats) {
mStats.noteDeviceIdleModeLocked(mode, activeReason, activeUid);
+ StatsLog.write(StatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mode);
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index c62cc38..aa82d00 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1198,11 +1198,6 @@
+ " (uid " + r.callingUid + ")");
skip = true;
}
- if (!skip) {
- r.manifestCount++;
- } else {
- r.manifestSkipCount++;
- }
if (r.curApp != null && r.curApp.crashing) {
// If the target process is crashing, just skip it.
Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r
@@ -1283,6 +1278,16 @@
}
}
+ if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction())
+ && !mService.mUserController
+ .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
+ 0 /* flags */)) {
+ skip = true;
+ Slog.w(TAG,
+ "Skipping delivery to " + info.activityInfo.packageName + " / "
+ + info.activityInfo.applicationInfo.uid + " : user is not running");
+ }
+
if (skip) {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Skipping delivery of ordered [" + mQueueName + "] "
@@ -1291,9 +1296,11 @@
r.receiver = null;
r.curFilter = null;
r.state = BroadcastRecord.IDLE;
+ r.manifestSkipCount++;
scheduleBroadcastsLocked();
return;
}
+ r.manifestCount++;
r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
r.state = BroadcastRecord.APP_RECEIVE;
@@ -1302,7 +1309,7 @@
if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
+ info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
- + info.activityInfo.applicationInfo.uid);
+ + receiverUid);
}
if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) {
@@ -1365,7 +1372,7 @@
// and mark the broadcast record as ready for the next.
Slog.w(TAG, "Unable to launch app "
+ info.activityInfo.applicationInfo.packageName + "/"
- + info.activityInfo.applicationInfo.uid + " for broadcast "
+ + receiverUid + " for broadcast "
+ r.intent + ": process is bad");
logBroadcastReceiverDiscardLocked(r);
finishReceiverLocked(r, r.resultCode, r.resultData,
diff --git a/services/core/java/com/android/server/am/LaunchingActivityPositioner.java b/services/core/java/com/android/server/am/LaunchingActivityPositioner.java
new file mode 100644
index 0000000..d5f9cf3
--- /dev/null
+++ b/services/core/java/com/android/server/am/LaunchingActivityPositioner.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner;
+
+/**
+ * An implementation of {@link LaunchingBoundsPositioner}, which applies the launch bounds specified
+ * inside {@link ActivityOptions#getLaunchBounds()}.
+ */
+public class LaunchingActivityPositioner implements LaunchingBoundsPositioner {
+ private final ActivityStackSupervisor mSupervisor;
+
+ LaunchingActivityPositioner(ActivityStackSupervisor activityStackSupervisor) {
+ mSupervisor = activityStackSupervisor;
+ }
+
+ @Override
+ public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout,
+ ActivityRecord activity, ActivityRecord source,
+ ActivityOptions options, Rect current, Rect result) {
+ // We only care about figuring out bounds for activities.
+ if (activity == null) {
+ return RESULT_SKIP;
+ }
+
+ // Activity must be resizeable in the specified task.
+ if (!(mSupervisor.canUseActivityOptionsLaunchBounds(options)
+ && (activity.isResizeable() || (task != null && task.isResizeable())))) {
+ return RESULT_SKIP;
+ }
+
+ final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+
+ // Bounds weren't valid.
+ if (bounds == null) {
+ return RESULT_SKIP;
+ }
+
+ result.set(bounds);
+
+ // When this is the most explicit position specification so we should not allow further
+ // modification of the position.
+ return RESULT_DONE;
+ }
+}
diff --git a/services/core/java/com/android/server/am/LaunchingBoundsController.java b/services/core/java/com/android/server/am/LaunchingBoundsController.java
new file mode 100644
index 0000000..c762f7f
--- /dev/null
+++ b/services/core/java/com/android/server/am/LaunchingBoundsController.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import android.annotation.IntDef;
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo.WindowLayout;
+import android.graphics.Rect;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP;
+
+/**
+ * {@link LaunchingBoundsController} calculates the launch bounds by coordinating between registered
+ * {@link LaunchingBoundsPositioner}.
+ */
+class LaunchingBoundsController {
+ private final List<LaunchingBoundsPositioner> mPositioners = new ArrayList<>();
+
+ // Temporary {@link Rect} for calculations. This is kept separate from {@code mTmpCurrent} and
+ // {@code mTmpResult} to prevent clobbering values.
+ private final Rect mTmpRect = new Rect();
+
+ private final Rect mTmpCurrent = new Rect();
+ private final Rect mTmpResult = new Rect();
+
+ /**
+ * Creates a {@link LaunchingBoundsController} with default registered
+ * {@link LaunchingBoundsPositioner}s.
+ */
+ void registerDefaultPositioners(ActivityStackSupervisor supervisor) {
+ // {@link LaunchingTaskPositioner} handles window layout preferences.
+ registerPositioner(new LaunchingTaskPositioner());
+
+ // {@link LaunchingActivityPositioner} is the most specific positioner and thus should be
+ // registered last (applied first) out of the defaults.
+ registerPositioner(new LaunchingActivityPositioner(supervisor));
+ }
+
+ /**
+ * Returns the position calculated by the registered positioners
+ * @param task The {@link TaskRecord} currently being positioned.
+ * @param layout The specified {@link WindowLayout}.
+ * @param activity The {@link ActivityRecord} currently being positioned.
+ * @param source The {@link ActivityRecord} from which activity was started from.
+ * @param options The {@link ActivityOptions} specified for the activity.
+ * @param result The resulting bounds. If no bounds are set, {@link Rect#isEmpty()} will be
+ * {@code true}.
+ */
+ void calculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+ ActivityRecord source, ActivityOptions options, Rect result) {
+ result.setEmpty();
+
+ // We start at the last registered {@link LaunchingBoundsPositioner} as this represents
+ // The positioner closest to the product level. Moving back through the list moves closer to
+ // the platform logic.
+ for (int i = mPositioners.size() - 1; i >= 0; --i) {
+ mTmpResult.setEmpty();
+ mTmpCurrent.set(result);
+ final LaunchingBoundsPositioner positioner = mPositioners.get(i);
+
+ switch(positioner.onCalculateBounds(task, layout, activity, source, options,
+ mTmpCurrent, mTmpResult)) {
+ case RESULT_SKIP:
+ // Do not apply any results when we are told to skip
+ continue;
+ case RESULT_DONE:
+ // Set result and return immediately.
+ result.set(mTmpResult);
+ return;
+ case RESULT_CONTINUE:
+ // Set result and continue
+ result.set(mTmpResult);
+ break;
+ }
+ }
+ }
+
+ /**
+ * A convenience method for laying out a task.
+ * @return {@code true} if bounds were set on the task. {@code false} otherwise.
+ */
+ boolean layoutTask(TaskRecord task, WindowLayout layout) {
+ calculateBounds(task, layout, null /*activity*/, null /*source*/, null /*options*/,
+ mTmpRect);
+
+ if (mTmpRect.isEmpty()) {
+ return false;
+ }
+
+ task.updateOverrideConfiguration(mTmpRect);
+
+ return true;
+ }
+
+ /**
+ * Adds a positioner to participate in future bounds calculation. Note that the last registered
+ * {@link LaunchingBoundsPositioner} will be the first to calculate the bounds.
+ */
+ void registerPositioner(LaunchingBoundsPositioner positioner) {
+ if (mPositioners.contains(positioner)) {
+ return;
+ }
+
+ mPositioners.add(positioner);
+ }
+
+ /**
+ * An interface implemented by those wanting to participate in bounds calculation.
+ */
+ interface LaunchingBoundsPositioner {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE})
+ @interface Result {}
+
+ // Returned when the positioner does not want to influence the bounds calculation
+ int RESULT_SKIP = 0;
+ // Returned when the positioner has changed the bounds and would like its results to be the
+ // final bounds applied.
+ int RESULT_DONE = 1;
+ // Returned when the positioner has changed the bounds but is okay with other positioners
+ // influencing the bounds.
+ int RESULT_CONTINUE = 2;
+
+ /**
+ * Called when asked to calculate bounds.
+ * @param task The {@link TaskRecord} currently being positioned.
+ * @param layout The specified {@link WindowLayout}.
+ * @param activity The {@link ActivityRecord} currently being positioned.
+ * @param source The {@link ActivityRecord} activity was started from.
+ * @param options The {@link ActivityOptions} specified for the activity.
+ * @param current The current bounds. This can differ from the initial bounds as it
+ * represents the modified bounds up to this point.
+ * @param result The {@link Rect} which the positioner should return its modified bounds.
+ * Any merging of the current bounds should be already applied to this
+ * value as well before returning.
+ * @return A {@link Result} representing the result of the bounds calculation.
+ */
+ @Result
+ int onCalculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+ ActivityRecord source, ActivityOptions options, Rect current, Rect result);
+ }
+}
diff --git a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
index 0dc73e9..c958fca 100644
--- a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
+++ b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
@@ -19,7 +19,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
@@ -36,8 +36,10 @@
* and compares corners of the task with corners of existing tasks. If some two pairs of corners are
* sufficiently close enough, it shifts the bounds of the new task and tries again. When it exhausts
* all possible shifts, it gives up and puts the task in the original position.
+ *
+ * Note that the only gravities of concern are the corners and the center.
*/
-class LaunchingTaskPositioner {
+class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoundsPositioner {
private static final String TAG = TAG_WITH_CLASS_NAME ? "LaunchingTaskPositioner" : TAG_AM;
// Determines how close window frames/corners have to be to call them colliding.
@@ -74,44 +76,51 @@
* Tries to set task's bound in a way that it won't collide with any other task. By colliding
* we mean that two tasks have left-top corner very close to each other, so one might get
* obfuscated by the other one.
- *
- * @param task Task for which we want to find bounds that won't collide with other.
- * @param tasks Existing tasks with which we don't want to collide.
- * @param windowLayout Optional information from the client about how it would like to be sized
- * and positioned.
*/
- void updateDefaultBounds(TaskRecord task, ArrayList<TaskRecord> tasks,
- @Nullable ActivityInfo.WindowLayout windowLayout) {
+ @Override
+ public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout,
+ ActivityRecord activity, ActivityRecord source,
+ ActivityOptions options, Rect current, Rect result) {
+ // We can only apply positioning if we're in a freeform stack.
+ if (task == null || task.getStack() == null || !task.inFreeformWindowingMode()) {
+ return RESULT_SKIP;
+ }
+
+ final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks();
+
updateAvailableRect(task, mAvailableRect);
- if (windowLayout == null) {
- positionCenter(task, tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
- getFreeformHeight(mAvailableRect));
- return;
+ if (layout == null) {
+ positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
+ getFreeformHeight(mAvailableRect), result);
+ return RESULT_CONTINUE;
}
- int width = getFinalWidth(windowLayout, mAvailableRect);
- int height = getFinalHeight(windowLayout, mAvailableRect);
- int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
- int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+
+ int width = getFinalWidth(layout, mAvailableRect);
+ int height = getFinalHeight(layout, mAvailableRect);
+ int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
if (verticalGravity == Gravity.TOP) {
if (horizontalGravity == Gravity.RIGHT) {
- positionTopRight(task, tasks, mAvailableRect, width, height);
+ positionTopRight(tasks, mAvailableRect, width, height, result);
} else {
- positionTopLeft(task, tasks, mAvailableRect, width, height);
+ positionTopLeft(tasks, mAvailableRect, width, height, result);
}
} else if (verticalGravity == Gravity.BOTTOM) {
if (horizontalGravity == Gravity.RIGHT) {
- positionBottomRight(task, tasks, mAvailableRect, width, height);
+ positionBottomRight(tasks, mAvailableRect, width, height, result);
} else {
- positionBottomLeft(task, tasks, mAvailableRect, width, height);
+ positionBottomLeft(tasks, mAvailableRect, width, height, result);
}
} else {
// Some fancy gravity setting that we don't support yet. We just put the activity in the
// center.
- Slog.w(TAG, "Received unsupported gravity: " + windowLayout.gravity
+ Slog.w(TAG, "Received unsupported gravity: " + layout.gravity
+ ", positioning in the center instead.");
- positionCenter(task, tasks, mAvailableRect, width, height);
+ positionCenter(tasks, mAvailableRect, width, height, result);
}
+
+ return RESULT_CONTINUE;
}
private void updateAvailableRect(TaskRecord task, Rect availableRect) {
@@ -179,50 +188,50 @@
return height;
}
- private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks,
- Rect availableRect, int width, int height) {
+ private void positionBottomLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+ int height, Rect result) {
mTmpProposal.set(availableRect.left, availableRect.bottom - height,
availableRect.left + width, availableRect.bottom);
- position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
- SHIFT_POLICY_HORIZONTAL_RIGHT);
+ position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
+ result);
}
- private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks,
- Rect availableRect, int width, int height) {
+ private void positionBottomRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+ int height, Rect result) {
mTmpProposal.set(availableRect.right - width, availableRect.bottom - height,
availableRect.right, availableRect.bottom);
- position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
- SHIFT_POLICY_HORIZONTAL_LEFT);
+ position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
+ result);
}
- private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks,
- Rect availableRect, int width, int height) {
+ private void positionTopLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+ int height, Rect result) {
mTmpProposal.set(availableRect.left, availableRect.top,
availableRect.left + width, availableRect.top + height);
- position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
- SHIFT_POLICY_HORIZONTAL_RIGHT);
+ position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
+ result);
}
- private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks,
- Rect availableRect, int width, int height) {
+ private void positionTopRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+ int height, Rect result) {
mTmpProposal.set(availableRect.right - width, availableRect.top,
availableRect.right, availableRect.top + height);
- position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
- SHIFT_POLICY_HORIZONTAL_LEFT);
+ position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
+ result);
}
- private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks,
- Rect availableRect, int width, int height) {
+ private void positionCenter(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+ int height, Rect result) {
final int defaultFreeformLeft = getFreeformStartLeft(availableRect);
final int defaultFreeformTop = getFreeformStartTop(availableRect);
mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop,
defaultFreeformLeft + width, defaultFreeformTop + height);
- position(task, tasks, availableRect, mTmpProposal, ALLOW_RESTART,
- SHIFT_POLICY_DIAGONAL_DOWN);
+ position(tasks, availableRect, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN,
+ result);
}
- private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect availableRect,
- Rect proposal, boolean allowRestart, int shiftPolicy) {
+ private void position(ArrayList<TaskRecord> tasks, Rect availableRect,
+ Rect proposal, boolean allowRestart, int shiftPolicy, Rect result) {
mTmpOriginal.set(proposal);
boolean restarted = false;
while (boundsConflict(proposal, tasks)) {
@@ -252,7 +261,7 @@
break;
}
}
- task.updateOverrideConfiguration(proposal);
+ result.set(proposal);
}
private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) {
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index 1c094c1..e87b4e6 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -19,17 +19,11 @@
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.StatusBarManager.DISABLE_BACK;
-import static android.app.StatusBarManager.DISABLE_HOME;
-import static android.app.StatusBarManager.DISABLE_MASK;
-import static android.app.StatusBarManager.DISABLE_NONE;
-import static android.app.StatusBarManager.DISABLE_RECENT;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Context.DEVICE_POLICY_SERVICE;
import static android.content.Context.STATUS_BAR_SERVICE;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_CURRENT;
-import static android.provider.Settings.Secure.LOCK_TO_APP_EXIT_LOCKED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
@@ -46,7 +40,10 @@
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.StatusBarManager;
+import android.app.admin.DevicePolicyManager;
import android.app.admin.IDevicePolicyManager;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Binder;
import android.os.Debug;
@@ -55,6 +52,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -84,13 +82,39 @@
private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
@VisibleForTesting
- static final int STATUS_BAR_MASK_LOCKED = DISABLE_MASK
- & (~DISABLE_BACK);
+ static final int STATUS_BAR_MASK_LOCKED = StatusBarManager.DISABLE_MASK
+ & (~StatusBarManager.DISABLE_EXPAND)
+ & (~StatusBarManager.DISABLE_NOTIFICATION_TICKER)
+ & (~StatusBarManager.DISABLE_SYSTEM_INFO)
+ & (~StatusBarManager.DISABLE_BACK);
@VisibleForTesting
- static final int STATUS_BAR_MASK_PINNED = DISABLE_MASK
- & (~DISABLE_BACK)
- & (~DISABLE_HOME)
- & (~DISABLE_RECENT);
+ static final int STATUS_BAR_MASK_PINNED = StatusBarManager.DISABLE_MASK
+ & (~StatusBarManager.DISABLE_BACK)
+ & (~StatusBarManager.DISABLE_HOME)
+ & (~StatusBarManager.DISABLE_RECENT);
+
+ private static final SparseArray<Pair<Integer, Integer>> STATUS_BAR_FLAG_MAP_LOCKED;
+ static {
+ STATUS_BAR_FLAG_MAP_LOCKED = new SparseArray<>();
+
+ STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO,
+ new Pair<>(StatusBarManager.DISABLE_CLOCK, StatusBarManager.DISABLE2_SYSTEM_ICONS));
+
+ STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS,
+ new Pair<>(StatusBarManager.DISABLE_NOTIFICATION_ICONS
+ | StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
+ StatusBarManager.DISABLE2_NOTIFICATION_SHADE));
+
+ STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_HOME,
+ new Pair<>(StatusBarManager.DISABLE_HOME, StatusBarManager.DISABLE2_NONE));
+
+ STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS,
+ new Pair<>(StatusBarManager.DISABLE_RECENT, StatusBarManager.DISABLE2_NONE));
+
+ STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+ new Pair<>(StatusBarManager.DISABLE_NONE,
+ StatusBarManager.DISABLE2_GLOBAL_ACTIONS));
+ }
/** Tag used for disabling of keyguard */
private static final String LOCK_TASK_TAG = "Lock-to-App";
@@ -119,9 +143,17 @@
LockTaskNotify mLockTaskNotify;
/**
- * The chain of tasks in lockTask mode. The current frontmost task is at the top, and tasks
- * may be finished until there is only one entry left. If this is empty the system is not
- * in lockTask mode.
+ * The chain of tasks in LockTask mode, in the order of when they first entered LockTask mode.
+ *
+ * The first task in the list, which started the current LockTask session, is called the root
+ * task. It coincides with the Home task in a typical multi-app kiosk deployment. When there are
+ * more than one locked tasks, the root task can't be finished. Nor can it be moved to the back
+ * of the stack by {@link ActivityStack#moveTaskToBackLocked(int)};
+ *
+ * Calling {@link Activity#stopLockTask()} on the root task will finish all tasks but itself in
+ * this list, and the device will exit LockTask mode.
+ *
+ * The list is empty if LockTask is inactive.
*/
private final ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>();
@@ -131,11 +163,16 @@
private final SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
/**
+ * Features that are allowed by DPC to show during LockTask mode.
+ */
+ private final SparseArray<Integer> mLockTaskFeatures = new SparseArray<>();
+
+ /**
* Store the current lock task mode. Possible values:
* {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED},
* {@link ActivityManager#LOCK_TASK_MODE_PINNED}
*/
- private int mLockTaskModeState;
+ private int mLockTaskModeState = LOCK_TASK_MODE_NONE;
/**
* This is ActivityStackSupervisor's Handler.
@@ -170,8 +207,29 @@
* @return whether the given task is locked at the moment. Locked tasks cannot be moved to the
* back of the stack.
*/
- boolean checkLockedTask(TaskRecord task) {
- if (mLockTaskModeTasks.contains(task)) {
+ @VisibleForTesting
+ boolean isTaskLocked(TaskRecord task) {
+ return mLockTaskModeTasks.contains(task);
+ }
+
+ /**
+ * @return {@code true} whether this task first started the current LockTask session.
+ */
+ private boolean isRootTask(TaskRecord task) {
+ return mLockTaskModeTasks.indexOf(task) == 0;
+ }
+
+ /**
+ * @return whether the given activity is blocked from finishing, because it is the only activity
+ * of the last locked task and finishing it would mean that lock task mode is ended illegally.
+ */
+ boolean activityBlockedFromFinish(ActivityRecord activity) {
+ final TaskRecord task = activity.getTask();
+ if (activity == task.getRootActivity()
+ && activity == task.getTopActivity()
+ && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV
+ && isRootTask(task)) {
+ Slog.i(TAG, "Not finishing task in lock task mode");
showLockTaskToast();
return true;
}
@@ -179,20 +237,16 @@
}
/**
- * @return whether the given activity is blocked from finishing, because it is the root activity
- * of the last locked task and finishing it would mean that lock task mode is ended illegally.
+ * @return whether the given task can be moved to the back of the stack with
+ * {@link ActivityStack#moveTaskToBackLocked(int)}
+ * @see #mLockTaskModeTasks
*/
- boolean activityBlockedFromFinish(ActivityRecord activity) {
- TaskRecord task = activity.getTask();
- if (activity == task.getRootActivity()
- && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV
- && mLockTaskModeTasks.size() == 1
- && mLockTaskModeTasks.contains(task)) {
- Slog.i(TAG, "Not finishing task in lock task mode");
+ boolean canMoveTaskToBack(TaskRecord task) {
+ if (isRootTask(task)) {
showLockTaskToast();
- return true;
+ return false;
}
- return false;
+ return true;
}
/**
@@ -217,7 +271,7 @@
private boolean isLockTaskModeViolationInternal(TaskRecord task, boolean isNewClearTask) {
// TODO: Double check what's going on here. If the task is already in lock task mode, it's
// likely whitelisted, so will return false below.
- if (getLockedTask() == task && !isNewClearTask) {
+ if (isTaskLocked(task) && !isNewClearTask) {
// If the task is already at the top and won't be cleared, then allow the operation
return false;
}
@@ -241,108 +295,131 @@
/**
* Stop the current lock task mode.
*
- * @param isSystemInitiated indicates whether this request was initiated by the system via
- * {@link ActivityManagerService#stopSystemLockTaskMode()}.
+ * This is called by {@link ActivityManagerService} and performs various checks before actually
+ * finishing the locked task.
+ *
+ * @param task the task that requested the end of lock task mode ({@code null} for quitting app
+ * pinning mode)
+ * @param isSystemCaller indicates whether this request comes from the system via
+ * {@link ActivityManagerService#stopSystemLockTaskMode()}. If
+ * {@code true}, it means the user intends to stop pinned mode through UI;
+ * otherwise, it's called by an app and we need to stop locked or pinned
+ * mode, subject to checks.
* @param callingUid the caller that requested the end of lock task mode.
+ * @throws IllegalArgumentException if the calling task is invalid (e.g., {@code null} or not in
+ * foreground)
* @throws SecurityException if the caller is not authorized to stop the lock task mode, i.e. if
* they differ from the one that launched lock task mode.
*/
- void stopLockTaskMode(boolean isSystemInitiated, int callingUid) {
- final TaskRecord lockTask = getLockedTask();
- if (lockTask == null || mLockTaskModeState == LOCK_TASK_MODE_NONE) {
- // Our work here is done.
+ void stopLockTaskMode(@Nullable TaskRecord task, boolean isSystemCaller, int callingUid) {
+ if (mLockTaskModeState == LOCK_TASK_MODE_NONE) {
return;
}
- if (isSystemInitiated && mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
- // As system can only start app pinning, we also only let it unlock in this mode.
- showLockTaskToast();
+ if (isSystemCaller) {
+ if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+ clearLockedTasks("stopAppPinning");
+ } else {
+ Slog.e(TAG_LOCKTASK, "Attempted to stop LockTask with isSystemCaller=true");
+ showLockTaskToast();
+ }
+
+ } else {
+ // Ensure calling activity is not null
+ if (task == null) {
+ throw new IllegalArgumentException("can't stop LockTask for null task");
+ }
+
+ // Ensure the same caller for startLockTaskMode and stopLockTaskMode.
+ // It is possible lockTaskMode was started by the system process because
+ // android:lockTaskMode is set to a locking value in the application manifest
+ // instead of the app calling startLockTaskMode. In this case
+ // {@link TaskRecord.mLockTaskUid} will be 0, so we compare the callingUid to the
+ // {@link TaskRecord.effectiveUid} instead. Also caller with
+ // {@link MANAGE_ACTIVITY_STACKS} can stop any lock task.
+ if (callingUid != task.mLockTaskUid
+ && (task.mLockTaskUid != 0 || callingUid != task.effectiveUid)) {
+ throw new SecurityException("Invalid uid, expected " + task.mLockTaskUid
+ + " callingUid=" + callingUid + " effectiveUid=" + task.effectiveUid);
+ }
+
+ // We don't care if it's pinned or locked mode; this will stop it anyways.
+ clearLockedTask(task);
+ }
+ }
+
+ /**
+ * Clear all locked tasks and request the end of LockTask mode.
+ *
+ * This method is called by {@link UserController} when starting a new foreground user, and,
+ * unlike {@link #stopLockTaskMode(TaskRecord, boolean, int)}, it doesn't perform the checks.
+ */
+ void clearLockedTasks(String reason) {
+ if (DEBUG_LOCKTASK) Slog.i(TAG_LOCKTASK, "clearLockedTasks: " + reason);
+ if (!mLockTaskModeTasks.isEmpty()) {
+ clearLockedTask(mLockTaskModeTasks.get(0));
+ }
+ }
+
+ /**
+ * Clear one locked task from LockTask mode.
+ *
+ * If the requested task is the root task (see {@link #mLockTaskModeTasks}), then all locked
+ * tasks are cleared. Otherwise, only the requested task is cleared. LockTask mode is stopped
+ * when the last locked task is cleared.
+ *
+ * @param task the task to be cleared from LockTask mode.
+ */
+ void clearLockedTask(final TaskRecord task) {
+ if (task == null || mLockTaskModeTasks.isEmpty()) return;
+
+ if (task == mLockTaskModeTasks.get(0)) {
+ // We're removing the root task while there are other locked tasks. Therefore we should
+ // clear all locked tasks in reverse order.
+ for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx > 0; --taskNdx) {
+ clearLockedTask(mLockTaskModeTasks.get(taskNdx));
+ }
+ }
+
+ removeLockedTask(task);
+ if (mLockTaskModeTasks.isEmpty()) {
return;
}
-
- // Ensure the same caller for startLockTaskMode and stopLockTaskMode.
- // It is possible lockTaskMode was started by the system process because
- // android:lockTaskMode is set to a locking value in the application manifest
- // instead of the app calling startLockTaskMode. In this case
- // {@link TaskRecord.mLockTaskUid} will be 0, so we compare the callingUid to the
- // {@link TaskRecord.effectiveUid} instead. Also caller with
- // {@link MANAGE_ACTIVITY_STACKS} can stop any lock task.
- if (!isSystemInitiated && callingUid != lockTask.mLockTaskUid
- && (lockTask.mLockTaskUid != 0 || callingUid != lockTask.effectiveUid)) {
- throw new SecurityException("Invalid uid, expected " + lockTask.mLockTaskUid
- + " callingUid=" + callingUid + " effectiveUid=" + lockTask.effectiveUid);
- }
-
- clearLockTaskMode("stopLockTask");
+ task.performClearTaskLocked();
+ mSupervisor.resumeFocusedStackTopActivityLocked();
}
/**
* Remove the given task from the locked task list. If this was the last task in the list,
* lock task mode is stopped.
*/
- void removeLockedTask(final TaskRecord task) {
+ private void removeLockedTask(final TaskRecord task) {
if (!mLockTaskModeTasks.remove(task)) {
return;
}
- if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "removeLockedTask: removed " + task);
+ if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: removed " + task);
if (mLockTaskModeTasks.isEmpty()) {
- // Last one.
if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: task=" + task +
" last task, reverting locktask mode. Callers=" + Debug.getCallers(3));
mHandler.post(() -> performStopLockTask(task.userId));
}
}
- /**
- * Remove the topmost task from the locked task list. If this is the last task in the list, it
- * will result in the end of locked task mode.
- */
- void clearLockTaskMode(String reason) {
- // Take out of lock task mode if necessary
- final TaskRecord lockedTask = getLockedTask();
- if (lockedTask != null) {
- removeLockedTask(lockedTask);
- if (!mLockTaskModeTasks.isEmpty()) {
- // There are locked tasks remaining, can only finish this task, not unlock it.
- if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
- "setLockTaskMode: Tasks remaining, can't unlock");
- lockedTask.performClearTaskLocked();
- mSupervisor.resumeFocusedStackTopActivityLocked();
- return;
- }
- }
- if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
- "setLockTaskMode: No tasks to unlock. Callers=" + Debug.getCallers(4));
- }
-
// This method should only be called on the handler thread
private void performStopLockTask(int userId) {
// When lock task ends, we enable the status bars.
try {
- if (getStatusBarService() != null) {
- getStatusBarService().disable(DISABLE_NONE, mToken,
- mContext.getPackageName());
+ setStatusBarState(LOCK_TASK_MODE_NONE, userId);
+ setKeyguardState(LOCK_TASK_MODE_NONE, userId);
+ if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+ lockKeyguardIfNeeded();
}
- mWindowManager.reenableKeyguard(mToken);
if (getDevicePolicyManager() != null) {
getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
}
if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
getLockTaskNotify().showPinningExitToast();
}
- try {
- boolean shouldLockKeyguard = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- LOCK_TO_APP_EXIT_LOCKED,
- USER_CURRENT) != 0;
- if (mLockTaskModeState == LOCK_TASK_MODE_PINNED && shouldLockKeyguard) {
- mWindowManager.lockNow(null);
- mWindowManager.dismissKeyguard(null /* callback */);
- getLockPatternUtils().requireCredentialEntry(USER_ALL);
- }
- } catch (Settings.SettingNotFoundException e) {
- // No setting, don't lock.
- }
} catch (RemoteException ex) {
throw new RuntimeException(ex);
} finally {
@@ -366,17 +443,18 @@
* Method to start lock task mode on a given task.
*
* @param task the task that should be locked.
- * @param isSystemInitiated indicates whether this request was initiated by the system via
- * {@link ActivityManagerService#startSystemLockTaskMode(int)}.
+ * @param isSystemCaller indicates whether this request was initiated by the system via
+ * {@link ActivityManagerService#startSystemLockTaskMode(int)}. If
+ * {@code true}, this intends to start pinned mode; otherwise, we look
+ * at the calling task's mLockTaskAuth to decide which mode to start.
* @param callingUid the caller that requested the launch of lock task mode.
*/
- void startLockTaskMode(@NonNull TaskRecord task, boolean isSystemInitiated,
- int callingUid) {
- if (!isSystemInitiated) {
+ void startLockTaskMode(@NonNull TaskRecord task, boolean isSystemCaller, int callingUid) {
+ if (!isSystemCaller) {
task.mLockTaskUid = callingUid;
if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
// startLockTask() called by app, but app is not part of lock task whitelist. Show
- // app pinning request. We will come back here with isSystemInitiated true.
+ // app pinning request. We will come back here with isSystemCaller true.
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Mode default, asking user");
StatusBarManagerInternal statusBarManager = LocalServices.getService(
StatusBarManagerInternal.class);
@@ -388,8 +466,9 @@
}
// System can only initiate screen pinning, not full lock task mode
- if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, isSystemInitiated ? "Locking pinned" : "Locking fully");
- setLockTaskMode(task, isSystemInitiated ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
+ if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
+ isSystemCaller ? "Locking pinned" : "Locking fully");
+ setLockTaskMode(task, isSystemCaller ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
"startLockTask", true);
}
@@ -418,19 +497,19 @@
task.userId,
lockTaskModeState));
}
-
- // Add it or move it to the top.
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "setLockTaskMode: Locking to " + task +
" Callers=" + Debug.getCallers(4));
- mLockTaskModeTasks.remove(task);
- mLockTaskModeTasks.add(task);
+
+ if (!mLockTaskModeTasks.contains(task)) {
+ mLockTaskModeTasks.add(task);
+ }
if (task.mLockTaskUid == -1) {
task.mLockTaskUid = task.effectiveUid;
}
if (andResume) {
- mSupervisor.findTaskToMoveToFrontLocked(task, 0, null, reason,
+ mSupervisor.findTaskToMoveToFront(task, 0, null, reason,
lockTaskModeState != LOCK_TASK_MODE_NONE);
mSupervisor.resumeFocusedStackTopActivityLocked();
mWindowManager.executeAppTransition();
@@ -448,16 +527,8 @@
getLockTaskNotify().showPinningStartToast();
}
mLockTaskModeState = lockTaskModeState;
- if (getStatusBarService() != null) {
- int flags = 0;
- if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
- flags = STATUS_BAR_MASK_LOCKED;
- } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
- flags = STATUS_BAR_MASK_PINNED;
- }
- getStatusBarService().disable(flags, mToken, mContext.getPackageName());
- }
- mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+ setStatusBarState(lockTaskModeState, userId);
+ setKeyguardState(lockTaskModeState, userId);
if (getDevicePolicyManager() != null) {
getDevicePolicyManager().notifyLockTaskModeChanged(true, packageName, userId);
}
@@ -536,14 +607,131 @@
}
/**
- * @return the topmost locked task
+ * Update the UI features that are enabled for LockTask mode.
+ * @param userId Which user these feature flags are associated with
+ * @param flags Bitfield of feature flags
+ * @see DevicePolicyManager#setLockTaskFeatures(ComponentName, int)
*/
- private TaskRecord getLockedTask() {
- final int top = mLockTaskModeTasks.size() - 1;
- if (top >= 0) {
- return mLockTaskModeTasks.get(top);
+ void updateLockTaskFeatures(int userId, int flags) {
+ int oldFlags = getLockTaskFeaturesForUser(userId);
+ if (flags == oldFlags) {
+ return;
}
- return null;
+
+ mLockTaskFeatures.put(userId, flags);
+ if (!mLockTaskModeTasks.isEmpty() && userId == mLockTaskModeTasks.get(0).userId) {
+ mHandler.post(() -> {
+ if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
+ setStatusBarState(mLockTaskModeState, userId);
+ setKeyguardState(mLockTaskModeState, userId);
+ }
+ });
+ }
+ }
+
+ /**
+ * Helper method for configuring the status bar disabled state.
+ * Should only be called on the handler thread to avoid race.
+ */
+ private void setStatusBarState(int lockTaskModeState, int userId) {
+ IStatusBarService statusBar = getStatusBarService();
+ if (statusBar == null) {
+ Slog.e(TAG, "Can't find StatusBarService");
+ return;
+ }
+
+ // Default state, when lockTaskModeState == LOCK_TASK_MODE_NONE
+ int flags1 = StatusBarManager.DISABLE_NONE;
+ int flags2 = StatusBarManager.DISABLE2_NONE;
+
+ if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
+ flags1 = STATUS_BAR_MASK_PINNED;
+
+ } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) {
+ int lockTaskFeatures = getLockTaskFeaturesForUser(userId);
+ Pair<Integer, Integer> statusBarFlags = getStatusBarDisableFlags(lockTaskFeatures);
+ flags1 = statusBarFlags.first;
+ flags2 = statusBarFlags.second;
+ }
+
+ try {
+ statusBar.disable(flags1, mToken, mContext.getPackageName());
+ statusBar.disable2(flags2, mToken, mContext.getPackageName());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set status bar flags", e);
+ }
+ }
+
+ /**
+ * Helper method for configuring the keyguard disabled state.
+ * Should only be called on the handler thread to avoid race.
+ */
+ private void setKeyguardState(int lockTaskModeState, int userId) {
+ if (lockTaskModeState == LOCK_TASK_MODE_NONE) {
+ mWindowManager.reenableKeyguard(mToken);
+
+ } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) {
+ int lockTaskFeatures = getLockTaskFeaturesForUser(userId);
+ if ((DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD & lockTaskFeatures) == 0) {
+ mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+ } else {
+ mWindowManager.reenableKeyguard(mToken);
+ }
+
+ } else { // lockTaskModeState == LOCK_TASK_MODE_PINNED
+ mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+ }
+ }
+
+ /**
+ * Helper method for locking the device immediately. This may be necessary when the device
+ * leaves the pinned mode.
+ */
+ private void lockKeyguardIfNeeded() {
+ try {
+ boolean shouldLockKeyguard = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
+ USER_CURRENT) != 0;
+ if (shouldLockKeyguard) {
+ mWindowManager.lockNow(null);
+ mWindowManager.dismissKeyguard(null /* callback */);
+ getLockPatternUtils().requireCredentialEntry(USER_ALL);
+ }
+ } catch (Settings.SettingNotFoundException e) {
+ // No setting, don't lock.
+ }
+ }
+
+ /**
+ * Translates from LockTask feature flags to StatusBarManager disable and disable2 flags.
+ * @param lockTaskFlags Bitfield of flags as per
+ * {@link DevicePolicyManager#setLockTaskFeatures(ComponentName, int)}
+ * @return A {@link Pair} of {@link StatusBarManager#disable(int)} and
+ * {@link StatusBarManager#disable2(int)} flags
+ */
+ @VisibleForTesting
+ Pair<Integer, Integer> getStatusBarDisableFlags(int lockTaskFlags) {
+ // Everything is disabled by default
+ int flags1 = StatusBarManager.DISABLE_MASK;
+ int flags2 = StatusBarManager.DISABLE2_MASK;
+ for (int i = STATUS_BAR_FLAG_MAP_LOCKED.size() - 1; i >= 0; i--) {
+ Pair<Integer, Integer> statusBarFlags = STATUS_BAR_FLAG_MAP_LOCKED.valueAt(i);
+ if ((STATUS_BAR_FLAG_MAP_LOCKED.keyAt(i) & lockTaskFlags) != 0) {
+ flags1 &= ~statusBarFlags.first;
+ flags2 &= ~statusBarFlags.second;
+ }
+ }
+ // Some flags are not used for LockTask purposes, so we mask them
+ flags1 &= STATUS_BAR_MASK_LOCKED;
+ return new Pair<>(flags1, flags2);
+ }
+
+ /**
+ * Gets the cached value of LockTask feature flags for a specific user.
+ */
+ private int getLockTaskFeaturesForUser(int userId) {
+ return mLockTaskFeatures.get(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
}
// Should only be called on the handler thread
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index ed3f503..d35c37b 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -37,8 +37,6 @@
import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
-import com.google.android.collect.Sets;
-
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.content.ComponentName;
@@ -58,15 +56,16 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArraySet;
-import android.util.MutableBoolean;
-import android.util.MutableInt;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.am.ActivityStack.ActivityState;
+import com.android.server.am.TaskRecord.TaskActivitiesReport;
+
+import com.google.android.collect.Sets;
import java.io.File;
import java.io.PrintWriter;
@@ -75,7 +74,6 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -126,6 +124,13 @@
private final UserController mUserController;
/**
+ * Keeps track of the static recents package/component which is granted additional permissions
+ * to call recents-related APIs.
+ */
+ private int mRecentsUid = -1;
+ private ComponentName mRecentsComponent = null;
+
+ /**
* Mapping of user id -> whether recent tasks have been loaded for that user.
*/
private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(
@@ -154,6 +159,7 @@
private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>();
private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>();
private final SparseBooleanArray mTmpQuietProfileUserIds = new SparseBooleanArray();
+ private final TaskActivitiesReport mTmpReport = new TaskActivitiesReport();
@VisibleForTesting
RecentTasks(ActivityManagerService service, TaskPersister taskPersister,
@@ -173,7 +179,7 @@
mTaskPersister = new TaskPersister(systemDir, stackSupervisor, service, this);
mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic();
mHasVisibleRecentTasks = res.getBoolean(com.android.internal.R.bool.config_hasRecents);
- loadParametersFromResources(service.mContext.getResources());
+ loadParametersFromResources(res);
}
@VisibleForTesting
@@ -217,6 +223,61 @@
: -1;
}
+ /**
+ * Loads the static recents component. This is called after the system is ready, but before
+ * any dependent services (like SystemUI) is started.
+ */
+ void loadRecentsComponent(Resources res) {
+ final String rawRecentsComponent = res.getString(
+ com.android.internal.R.string.config_recentsComponentName);
+ if (TextUtils.isEmpty(rawRecentsComponent)) {
+ return;
+ }
+
+ final ComponentName cn = ComponentName.unflattenFromString(rawRecentsComponent);
+ if (cn != null) {
+ try {
+ final ApplicationInfo appInfo = AppGlobals.getPackageManager()
+ .getApplicationInfo(cn.getPackageName(), 0, mService.mContext.getUserId());
+ if (appInfo != null) {
+ mRecentsUid = appInfo.uid;
+ mRecentsComponent = cn;
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Could not load application info for recents component: " + cn);
+ }
+ }
+ }
+
+ /**
+ * @return whether the current caller has the same uid as the recents component.
+ */
+ boolean isCallerRecents(int callingUid) {
+ return UserHandle.isSameApp(callingUid, mRecentsUid);
+ }
+
+ /**
+ * @return whether the given component is the recents component and shares the same uid as the
+ * recents component.
+ */
+ boolean isRecentsComponent(ComponentName cn, int uid) {
+ return cn.equals(mRecentsComponent) && UserHandle.isSameApp(uid, mRecentsUid);
+ }
+
+ /**
+ * @return the recents component.
+ */
+ ComponentName getRecentsComponent() {
+ return mRecentsComponent;
+ }
+
+ /**
+ * @return the uid for the recents component.
+ */
+ int getRecentsComponentUid() {
+ return mRecentsUid;
+ }
+
void registerCallback(Callbacks callback) {
mCallbacks.add(callback);
}
@@ -339,6 +400,7 @@
}
void onSystemReadyLocked() {
+ loadRecentsComponent(mService.mContext.getResources());
mTasks.clear();
mTaskPersister.startPersisting();
}
@@ -690,7 +752,7 @@
continue;
}
- ActivityManager.RecentTaskInfo rti = RecentTasks.createRecentTaskInfo(tr);
+ final ActivityManager.RecentTaskInfo rti = createRecentTaskInfo(tr);
if (!getDetailedTasks) {
rti.baseIntent.replaceExtras((Bundle)null);
}
@@ -1327,12 +1389,14 @@
void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) {
pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)");
+ pw.println("mRecentsUid=" + mRecentsUid);
+ pw.println("mRecentsComponent=" + mRecentsComponent);
if (mTasks.isEmpty()) {
return;
}
- final MutableBoolean printedAnything = new MutableBoolean(false);
- final MutableBoolean printedHeader = new MutableBoolean(false);
+ boolean printedAnything = false;
+ boolean printedHeader = false;
final int size = mTasks.size();
for (int i = 0; i < size; i++) {
final TaskRecord tr = mTasks.get(i);
@@ -1341,10 +1405,10 @@
continue;
}
- if (!printedHeader.value) {
+ if (!printedHeader) {
pw.println(" Recent tasks:");
- printedHeader.value = true;
- printedAnything.value = true;
+ printedHeader = true;
+ printedAnything = true;
}
pw.print(" * Recent #"); pw.print(i); pw.print(": ");
pw.println(tr);
@@ -1353,7 +1417,7 @@
}
}
- if (!printedAnything.value) {
+ if (!printedAnything) {
pw.println(" (nothing)");
}
}
@@ -1361,7 +1425,7 @@
/**
* Creates a new RecentTaskInfo from a TaskRecord.
*/
- static ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) {
+ ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) {
// Update the task description to reflect any changes in the task stack
tr.updateTaskDescription();
@@ -1387,24 +1451,10 @@
rti.resizeMode = tr.mResizeMode;
rti.configuration.setTo(tr.getConfiguration());
- ActivityRecord base = null;
- ActivityRecord top = null;
- ActivityRecord tmp;
-
- for (int i = tr.mActivities.size() - 1; i >= 0; --i) {
- tmp = tr.mActivities.get(i);
- if (tmp.finishing) {
- continue;
- }
- base = tmp;
- if (top == null || (top.state == ActivityState.INITIALIZING)) {
- top = base;
- }
- rti.numActivities++;
- }
-
- rti.baseActivity = (base != null) ? base.intent.getComponent() : null;
- rti.topActivity = (top != null) ? top.intent.getComponent() : null;
+ tr.getNumRunningActivities(mTmpReport);
+ rti.numActivities = mTmpReport.numActivities;
+ rti.baseActivity = (mTmpReport.base != null) ? mTmpReport.base.intent.getComponent() : null;
+ rti.topActivity = (mTmpReport.top != null) ? mTmpReport.top.intent.getComponent() : null;
return rti;
}
diff --git a/services/core/java/com/android/server/am/RunningTasks.java b/services/core/java/com/android/server/am/RunningTasks.java
new file mode 100644
index 0000000..c860df8
--- /dev/null
+++ b/services/core/java/com/android/server/am/RunningTasks.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeSet;
+
+/**
+ * Class for resolving the set of running tasks in the system.
+ */
+class RunningTasks {
+
+ // Comparator to sort by last active time (descending)
+ private static final Comparator<TaskRecord> LAST_ACTIVE_TIME_COMPARATOR =
+ (o1, o2) -> Long.signum(o2.lastActiveTime - o1.lastActiveTime);
+
+ private final TaskRecord.TaskActivitiesReport mTmpReport =
+ new TaskRecord.TaskActivitiesReport();
+ private final TreeSet<TaskRecord> mTmpSortedSet = new TreeSet<>(LAST_ACTIVE_TIME_COMPARATOR);
+ private final ArrayList<TaskRecord> mTmpStackTasks = new ArrayList<>();
+
+ void getTasks(int maxNum, List<RunningTaskInfo> list, @ActivityType int ignoreActivityType,
+ @WindowingMode int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
+ int callingUid, boolean allowed) {
+ // Return early if there are no tasks to fetch
+ if (maxNum <= 0) {
+ return;
+ }
+
+ // Gather all of the tasks across all of the tasks, and add them to the sorted set
+ mTmpSortedSet.clear();
+ mTmpStackTasks.clear();
+ final int numDisplays = activityDisplays.size();
+ for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+ final ActivityDisplay display = activityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ stack.getRunningTasks(mTmpStackTasks, ignoreActivityType, ignoreWindowingMode,
+ callingUid, allowed);
+ for (int i = mTmpStackTasks.size() - 1; i >= 0; i--) {
+ mTmpSortedSet.addAll(mTmpStackTasks);
+ }
+ }
+ }
+
+ // Take the first {@param maxNum} tasks and create running task infos for them
+ final Iterator<TaskRecord> iter = mTmpSortedSet.iterator();
+ while (iter.hasNext()) {
+ if (maxNum == 0) {
+ break;
+ }
+
+ final TaskRecord task = iter.next();
+ list.add(createRunningTaskInfo(task));
+ maxNum--;
+ }
+ }
+
+ /**
+ * Constructs a {@link RunningTaskInfo} from a given {@param task}.
+ */
+ private RunningTaskInfo createRunningTaskInfo(TaskRecord task) {
+ task.getNumRunningActivities(mTmpReport);
+
+ final RunningTaskInfo ci = new RunningTaskInfo();
+ ci.id = task.taskId;
+ ci.stackId = task.getStackId();
+ ci.baseActivity = mTmpReport.base.intent.getComponent();
+ ci.topActivity = mTmpReport.top.intent.getComponent();
+ ci.lastActiveTime = task.lastActiveTime;
+ ci.description = task.lastDescription;
+ ci.numActivities = mTmpReport.numActivities;
+ ci.numRunning = mTmpReport.numRunning;
+ ci.supportsSplitScreenMultiWindow = task.supportsSplitScreenWindowingMode();
+ ci.resizeMode = task.mResizeMode;
+ ci.configuration.setTo(task.getConfiguration());
+ return ci;
+ }
+}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index c451235..1a4f9d4 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -30,7 +30,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
@@ -74,7 +76,6 @@
import static com.android.server.am.proto.TaskRecordProto.ORIG_ACTIVITY;
import static com.android.server.am.proto.TaskRecordProto.REAL_ACTIVITY;
import static com.android.server.am.proto.TaskRecordProto.RESIZE_MODE;
-import static com.android.server.am.proto.TaskRecordProto.RETURN_TO_TYPE;
import static com.android.server.am.proto.TaskRecordProto.STACK_ID;
import static com.android.server.am.proto.TaskRecordProto.ACTIVITY_TYPE;
@@ -111,6 +112,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.util.XmlUtils;
+import com.android.server.am.ActivityStack.ActivityState;
import com.android.server.wm.AppWindowContainerController;
import com.android.server.wm.ConfigurationContainer;
import com.android.server.wm.StackWindowController;
@@ -172,7 +174,6 @@
// Current version of the task record we persist. Used to check if we need to run any upgrade
// code.
private static final int PERSIST_TASK_VERSION = 1;
- private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
static final int INVALID_TASK_ID = -1;
private static final int INVALID_MIN_SIZE = -1;
@@ -187,13 +188,13 @@
REPARENT_KEEP_STACK_AT_FRONT,
REPARENT_LEAVE_STACK_IN_PLACE
})
- public @interface ReparentMoveStackMode {}
+ @interface ReparentMoveStackMode {}
// Moves the stack to the front if it was not at the front
- public static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
+ static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
// Only moves the stack to the front if it was focused or front most already
- public static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
+ static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
// Do not move the stack as a part of reparenting
- public static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
+ static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
final int taskId; // Unique identifier for this task.
String affinity; // The affinity name for this task, or null; may change identity.
@@ -231,9 +232,6 @@
private boolean mSupportsPictureInPicture; // Whether or not this task and its activities
// support PiP. Based on the {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag
// of the root activity.
- boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize
- // changes on a temporary basis.
-
/** Can't be put in lockTask mode. */
final static int LOCK_TASK_AUTH_DONT_LOCK = 0;
/** Can enter app pinning with user approval. Can never start over existing lockTask task. */
@@ -268,12 +266,6 @@
* (positive) or back (negative). Absolute value indicates time. */
long mLastTimeMoved = System.currentTimeMillis();
- /** Indication of what to run next when task exits. */
- // TODO: Shouldn't be needed if we have things in visual order. I.e. we stop using stacks or
- // have a stack per standard application type...
- /*@WindowConfiguration.ActivityType*/
- private int mTaskToReturnTo = ACTIVITY_TYPE_STANDARD;
-
/** If original intent did not allow relinquishing task identity, save that information */
private boolean mNeverRelinquishIdentity = true;
@@ -281,7 +273,6 @@
// do not want to delete the stack when the task goes empty.
private boolean mReuseTask = false;
- private final String mFilename;
CharSequence lastDescription; // Last description captured for this item.
int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
@@ -327,8 +318,6 @@
TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
mService = service;
- mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
- TaskPersister.IMAGE_EXTENSION;
userId = UserHandle.getUserId(info.applicationInfo.uid);
taskId = _taskId;
lastActiveTime = SystemClock.elapsedRealtime();
@@ -348,8 +337,6 @@
TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
TaskDescription _taskDescription) {
mService = service;
- mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
- TaskPersister.IMAGE_EXTENSION;
userId = UserHandle.getUserId(info.applicationInfo.uid);
taskId = _taskId;
lastActiveTime = SystemClock.elapsedRealtime();
@@ -368,7 +355,6 @@
maxRecents = Math.min(Math.max(info.maxRecents, 1),
ActivityManager.getMaxAppRecentsLimitStatic());
- mTaskToReturnTo = ACTIVITY_TYPE_HOME;
lastTaskDescription = _taskDescription;
touchActiveTime();
mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity);
@@ -385,8 +371,6 @@
int resizeMode, boolean supportsPictureInPicture, boolean _realActivitySuspended,
boolean userSetupComplete, int minWidth, int minHeight) {
mService = service;
- mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
- TaskPersister.IMAGE_EXTENSION;
taskId = _taskId;
intent = _intent;
affinityIntent = _affinityIntent;
@@ -401,7 +385,6 @@
isAvailable = true;
autoRemoveRecents = _autoRemoveRecents;
askedCompatMode = _askedCompatMode;
- mTaskToReturnTo = ACTIVITY_TYPE_HOME;
userId = _userId;
mUserSetupComplete = userSetupComplete;
effectiveUid = _effectiveUid;
@@ -455,7 +438,7 @@
}
void removeWindowContainer() {
- mService.mLockTaskController.removeLockedTask(this);
+ mService.mLockTaskController.clearLockedTask(this);
mWindowContainerController.removeContainer();
if (!getWindowConfiguration().persistTaskBounds()) {
// Reset current bounds for task whose bounds shouldn't be persisted so it uses
@@ -707,7 +690,7 @@
} else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
Rect bounds = getLaunchBounds();
if (bounds == null) {
- toStack.layoutTaskInStack(this, null);
+ mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
bounds = mBounds;
}
kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
@@ -904,29 +887,9 @@
return this.intent.filterEquals(intent);
}
- void setTaskToReturnTo(/*@WindowConfiguration.ActivityType*/ int taskToReturnTo) {
- mTaskToReturnTo = taskToReturnTo == ACTIVITY_TYPE_RECENTS
- ? ACTIVITY_TYPE_HOME : taskToReturnTo;
- }
-
- void setTaskToReturnTo(ActivityRecord source) {
- if (source.isActivityTypeRecents()) {
- setTaskToReturnTo(ACTIVITY_TYPE_RECENTS);
- } else if (source.isActivityTypeAssistant()) {
- setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
- }
- }
-
- int getTaskToReturnTo() {
- return mTaskToReturnTo;
- }
-
- boolean returnsToHomeTask() {
- return mTaskToReturnTo == ACTIVITY_TYPE_HOME;
- }
-
- boolean returnsToStandardTask() {
- return mTaskToReturnTo == ACTIVITY_TYPE_STANDARD;
+ boolean returnsToHomeStack() {
+ final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
+ return (intent.getFlags() & returnHomeFlags) == returnHomeFlags;
}
void setPrevAffiliate(TaskRecord prevAffiliate) {
@@ -1098,6 +1061,36 @@
return null;
}
+ /**
+ * Return the number of running activities, and the number of non-finishing/initializing
+ * activities in the provided {@param reportOut} respectively.
+ */
+ void getNumRunningActivities(TaskActivitiesReport reportOut) {
+ reportOut.reset();
+ for (int i = mActivities.size() - 1; i >= 0; --i) {
+ final ActivityRecord r = mActivities.get(i);
+ if (r.finishing) {
+ continue;
+ }
+
+ reportOut.base = r;
+
+ // Increment the total number of non-finishing activities
+ reportOut.numActivities++;
+
+ if (reportOut.top == null || (reportOut.top.state == ActivityState.INITIALIZING)) {
+ reportOut.top = r;
+ // Reset the number of running activities until we hit the first non-initializing
+ // activity
+ reportOut.numRunning = 0;
+ }
+ if (r.app != null && r.app.thread != null) {
+ // Increment the number of actually running activities
+ reportOut.numRunning++;
+ }
+ }
+ }
+
boolean okToShowLocked() {
// NOTE: If {@link TaskRecord#topRunningActivityLocked} return is not null then it is
// okay to show the activity when locked.
@@ -1447,17 +1440,9 @@
" mLockTaskAuth=" + lockTaskAuthToString());
}
- boolean isOverHomeStack() {
- return mTaskToReturnTo == ACTIVITY_TYPE_HOME;
- }
-
- boolean isOverAssistantStack() {
- return mTaskToReturnTo == ACTIVITY_TYPE_ASSISTANT;
- }
-
private boolean isResizeable(boolean checkSupportsPip) {
return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode)
- || (checkSupportsPip && mSupportsPictureInPicture)) && !mTemporarilyUnresizable;
+ || (checkSupportsPip && mSupportsPictureInPicture));
}
boolean isResizeable() {
@@ -2089,7 +2074,7 @@
if (mLastNonFullscreenBounds != null) {
updateOverrideConfiguration(mLastNonFullscreenBounds);
} else {
- inStack.layoutTaskInStack(this, null);
+ mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
}
} else {
updateOverrideConfiguration(inStack.mBounds);
@@ -2164,13 +2149,11 @@
pw.print(prefix); pw.print("realActivity=");
pw.println(realActivity.flattenToShortString());
}
- if (autoRemoveRecents || isPersistable || !isActivityTypeStandard()
- || mTaskToReturnTo != ACTIVITY_TYPE_STANDARD || numFullscreen != 0) {
+ if (autoRemoveRecents || isPersistable || !isActivityTypeStandard() || numFullscreen != 0) {
pw.print(prefix); pw.print("autoRemoveRecents="); pw.print(autoRemoveRecents);
pw.print(" isPersistable="); pw.print(isPersistable);
pw.print(" numFullscreen="); pw.print(numFullscreen);
- pw.print(" activityType="); pw.print(getActivityType());
- pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
+ pw.print(" activityType="); pw.println(getActivityType());
}
if (rootWasReset || mNeverRelinquishIdentity || mReuseTask
|| mLockTaskAuth != LOCK_TASK_AUTH_PINNABLE) {
@@ -2253,7 +2236,7 @@
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- super.writeToProto(proto, CONFIGURATION_CONTAINER);
+ super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
proto.write(ID, taskId);
for (int i = mActivities.size() - 1; i >= 0; i--) {
ActivityRecord activity = mActivities.get(i);
@@ -2270,7 +2253,6 @@
proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString());
}
proto.write(ACTIVITY_TYPE, getActivityType());
- proto.write(RETURN_TO_TYPE, mTaskToReturnTo);
proto.write(RESIZE_MODE, mResizeMode);
proto.write(FULLSCREEN, mFullscreen);
if (mBounds != null) {
@@ -2280,4 +2262,19 @@
proto.write(MIN_HEIGHT, mMinHeight);
proto.end(token);
}
+
+ /**
+ * See {@link #getNumRunningActivities(TaskActivitiesReport)}.
+ */
+ static class TaskActivitiesReport {
+ int numRunning;
+ int numActivities;
+ ActivityRecord top;
+ ActivityRecord base;
+
+ void reset() {
+ numRunning = numActivities = 0;
+ top = base = null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 4aa8adb9..2df5dc9 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -117,20 +117,6 @@
class UserController implements Handler.Callback {
private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
- /**
- * Maximum number of users we allow to be running at a time, including the system user and
- * its profiles.
- * Note changing this to 2 is not recommended, since that would mean, if the user uses
- * work profile and then switch to a secondary user, then the work profile user would be killed,
- * which should work fine, but aggressively killing the work profile user that has just been
- * running could cause data loss. (Even without work profile, witching from secondary user A
- * to secondary user B would cause similar issues on user B.)
- *
- * TODO: Consider adding or replacing with "MAX_RUNNING_*SECONDARY*_USERS", which is the max
- * number of running *secondary, switchable* users.
- */
- static final int MAX_RUNNING_USERS = 3;
-
// Amount of time we wait for observers to handle a user switch before
// giving up on them and unfreezing the screen.
static final int USER_SWITCH_TIMEOUT_MS = 3 * 1000;
@@ -157,6 +143,17 @@
// when it never calls back.
private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
+ /**
+ * Maximum number of users we allow to be running at a time, including system user.
+ *
+ * <p>This parameter only affects how many background users will be stopped when switching to a
+ * new user. It has no impact on {@link #startUser(int, boolean)} behavior.
+ *
+ * <p>Note: Current and system user (and their related profiles) are never stopped when
+ * switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
+ */
+ int mMaxRunningUsers;
+
// Lock for internal state.
private final Object mLock = new Object();
@@ -245,26 +242,26 @@
finishUserBoot(uss);
startProfiles();
synchronized (mLock) {
- stopRunningUsersLU(MAX_RUNNING_USERS);
+ stopRunningUsersLU(mMaxRunningUsers);
}
}
void stopRunningUsersLU(int maxRunningUsers) {
- int num = mUserLru.size();
+ int currentlyRunning = mUserLru.size();
int i = 0;
- while (num > maxRunningUsers && i < mUserLru.size()) {
+ while (currentlyRunning > maxRunningUsers && i < mUserLru.size()) {
Integer oldUserId = mUserLru.get(i);
UserState oldUss = mStartedUsers.get(oldUserId);
if (oldUss == null) {
// Shouldn't happen, but be sane if it does.
mUserLru.remove(i);
- num--;
+ currentlyRunning--;
continue;
}
if (oldUss.state == UserState.STATE_STOPPING
|| oldUss.state == UserState.STATE_SHUTDOWN) {
// This user is already stopping, doesn't count.
- num--;
+ currentlyRunning--;
i++;
continue;
}
@@ -272,16 +269,15 @@
// Owner/System user and current user can't be stopped. We count it as running
// when it is not a pure system user.
if (UserInfo.isSystemOnly(oldUserId)) {
- num--;
+ currentlyRunning--;
}
i++;
continue;
}
// This is a user to be stopped.
- if (stopUsersLU(oldUserId, false, null) != USER_OP_SUCCESS) {
- num--;
+ if (stopUsersLU(oldUserId, false, null) == USER_OP_SUCCESS) {
+ currentlyRunning--;
}
- num--;
i++;
}
}
@@ -814,7 +810,7 @@
}
final int profilesToStartSize = profilesToStart.size();
int i = 0;
- for (; i < profilesToStartSize && i < (MAX_RUNNING_USERS - 1); ++i) {
+ for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) {
startUser(profilesToStart.get(i).id, /* foreground= */ false);
}
if (i < profilesToStartSize) {
@@ -876,9 +872,7 @@
}
if (foreground) {
- // TODO: I don't think this does what the caller think it does. Seems to only
- // remove one locked task and won't work if multiple locked tasks are present.
- mInjector.clearLockTaskMode("startUser");
+ mInjector.clearAllLockedTasks("startUser");
}
final UserInfo userInfo = getUserInfo(userId);
@@ -2021,7 +2015,7 @@
void loadUserRecents(int userId) {
synchronized (mService) {
- mService.mRecentTasks.loadUserRecentsLocked(userId);
+ mService.getRecentTasks().loadUserRecentsLocked(userId);
}
}
@@ -2057,9 +2051,9 @@
}
}
- protected void clearLockTaskMode(String reason) {
+ protected void clearAllLockedTasks(String reason) {
synchronized (mService) {
- mService.mLockTaskController.clearLockTaskMode(reason);
+ mService.mLockTaskController.clearLockedTasks(reason);
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
new file mode 100644
index 0000000..8981db1
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.net.LinkProperties;
+import android.net.metrics.DefaultNetworkEvent;
+import android.net.metrics.IpConnectivityLog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tracks events related to the default network for the purpose of default network metrics.
+ * {@hide}
+ */
+public class DefaultNetworkMetrics {
+
+ private static final int ROLLING_LOG_SIZE = 64;
+
+ // Event buffer used for metrics upload. The buffer is cleared when events are collected.
+ @GuardedBy("this")
+ private final List<DefaultNetworkEvent> mEvents = new ArrayList<>();
+
+ public synchronized void listEvents(PrintWriter pw) {
+ long localTimeMs = System.currentTimeMillis();
+ for (DefaultNetworkEvent ev : mEvents) {
+ pw.println(ev);
+ }
+ }
+
+ public synchronized void listEventsAsProto(PrintWriter pw) {
+ for (DefaultNetworkEvent ev : mEvents) {
+ pw.print(IpConnectivityEventBuilder.toProto(ev));
+ }
+ }
+
+ public synchronized void flushEvents(List<IpConnectivityEvent> out) {
+ for (DefaultNetworkEvent ev : mEvents) {
+ out.add(IpConnectivityEventBuilder.toProto(ev));
+ }
+ mEvents.clear();
+ }
+
+ public synchronized void logDefaultNetworkEvent(
+ NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
+ DefaultNetworkEvent ev = new DefaultNetworkEvent();
+ if (newNai != null) {
+ ev.netId = newNai.network().netId;
+ ev.transportTypes = newNai.networkCapabilities.getTransportTypes();
+ }
+ if (prevNai != null) {
+ ev.prevNetId = prevNai.network().netId;
+ final LinkProperties lp = prevNai.linkProperties;
+ ev.prevIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
+ ev.prevIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
+ }
+
+ mEvents.add(ev);
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 67e7216..3d71ecb 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -132,6 +132,18 @@
return out;
}
+ public static IpConnectivityEvent toProto(DefaultNetworkEvent in) {
+ IpConnectivityLogClass.DefaultNetworkEvent ev =
+ new IpConnectivityLogClass.DefaultNetworkEvent();
+ ev.networkId = netIdOf(in.netId);
+ ev.previousNetworkId = netIdOf(in.prevNetId);
+ ev.transportTypes = in.transportTypes;
+ ev.previousNetworkIpSupport = ipSupportOf(in);
+ final IpConnectivityEvent out = buildEvent(in.netId, 0, null);
+ out.setDefaultNetworkEvent(ev);
+ return out;
+ }
+
private static IpConnectivityEvent buildEvent(int netId, long transports, String ifname) {
final IpConnectivityEvent ev = new IpConnectivityEvent();
ev.networkId = netId;
@@ -164,11 +176,6 @@
return true;
}
- if (in instanceof DefaultNetworkEvent) {
- setDefaultNetworkEvent(out, (DefaultNetworkEvent) in);
- return true;
- }
-
if (in instanceof NetworkEvent) {
setNetworkEvent(out, (NetworkEvent) in);
return true;
@@ -225,16 +232,6 @@
out.setIpReachabilityEvent(ipReachabilityEvent);
}
- private static void setDefaultNetworkEvent(IpConnectivityEvent out, DefaultNetworkEvent in) {
- IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent =
- new IpConnectivityLogClass.DefaultNetworkEvent();
- defaultNetworkEvent.networkId = netIdOf(in.netId);
- defaultNetworkEvent.previousNetworkId = netIdOf(in.prevNetId);
- defaultNetworkEvent.transportTypes = in.transportTypes;
- defaultNetworkEvent.previousNetworkIpSupport = ipSupportOf(in);
- out.setDefaultNetworkEvent(defaultNetworkEvent);
- }
-
private static void setNetworkEvent(IpConnectivityEvent out, NetworkEvent in) {
IpConnectivityLogClass.NetworkEvent networkEvent =
new IpConnectivityLogClass.NetworkEvent();
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index f2445fa..24217e6 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -32,12 +32,15 @@
import android.util.ArrayMap;
import android.util.Base64;
import android.util.Log;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.RingBuffer;
import com.android.internal.util.TokenBucket;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
@@ -112,6 +115,9 @@
private final ToIntFunction<Context> mCapacityGetter;
+ @VisibleForTesting
+ final DefaultNetworkMetrics mDefaultNetworkMetrics = new DefaultNetworkMetrics();
+
public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
super(ctx);
mCapacityGetter = capacityGetter;
@@ -135,6 +141,8 @@
publishBinderService(SERVICE_NAME, impl);
publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener);
+
+ LocalServices.addService(Logger.class, new LoggerImpl());
}
}
@@ -188,6 +196,8 @@
final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events);
+ mDefaultNetworkMetrics.flushEvents(protoEvents);
+
if (mNetdListener != null) {
mNetdListener.flushStatistics(protoEvents);
}
@@ -228,6 +238,7 @@
if (mNetdListener != null) {
mNetdListener.listAsProtos(pw);
}
+ mDefaultNetworkMetrics.listEventsAsProto(pw);
return;
}
@@ -237,6 +248,7 @@
if (mNetdListener != null) {
mNetdListener.list(pw);
}
+ mDefaultNetworkMetrics.listEvents(pw);
}
/**
@@ -254,6 +266,7 @@
if (mNetdListener != null) {
mNetdListener.list(pw);
}
+ mDefaultNetworkMetrics.listEvents(pw);
}
private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -366,4 +379,15 @@
map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
return map;
}
+
+ /** Direct non-Binder interface for event producer clients within the system servers. */
+ public interface Logger {
+ DefaultNetworkMetrics defaultNetworkMetrics();
+ }
+
+ private class LoggerImpl implements Logger {
+ public DefaultNetworkMetrics defaultNetworkMetrics() {
+ return mDefaultNetworkMetrics;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 6206dfc..05c6e69 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -27,6 +27,7 @@
import android.net.metrics.DnsEvent;
import android.net.metrics.INetdEventListener;
import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.NetworkMetrics;
import android.net.metrics.WakeupEvent;
import android.net.metrics.WakeupStats;
import android.os.RemoteException;
@@ -34,6 +35,7 @@
import android.util.Log;
import android.util.ArrayMap;
import android.util.SparseArray;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.BitUtils;
@@ -41,10 +43,11 @@
import com.android.internal.util.RingBuffer;
import com.android.internal.util.TokenBucket;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
-import java.util.function.Function;
-import java.util.function.IntFunction;
+import java.util.StringJoiner;
/**
* Implementation of the INetdEventListener interface.
@@ -57,13 +60,13 @@
private static final boolean DBG = false;
private static final boolean VDBG = false;
- private static final int INITIAL_DNS_BATCH_SIZE = 100;
-
// Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum
// bursts of 5000 measurements.
private static final int CONNECT_LATENCY_BURST_LIMIT = 5000;
private static final int CONNECT_LATENCY_FILL_RATE = 15 * (int) DateUtils.SECOND_IN_MILLIS;
- private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
+
+ private static final long METRICS_SNAPSHOT_SPAN_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
+ private static final int METRICS_SNAPSHOT_BUFFER_SIZE = 48; // 4 hours
@VisibleForTesting
static final int WAKEUP_EVENT_BUFFER_LENGTH = 1024;
@@ -72,11 +75,15 @@
@VisibleForTesting
static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:";
- // Sparse arrays of DNS and connect events, grouped by net id.
+ // Array of aggregated DNS and connect events sent by netd, grouped by net id.
@GuardedBy("this")
- private final SparseArray<DnsEvent> mDnsEvents = new SparseArray<>();
+ private final SparseArray<NetworkMetrics> mNetworkMetrics = new SparseArray<>();
+
@GuardedBy("this")
- private final SparseArray<ConnectStats> mConnectEvents = new SparseArray<>();
+ private final RingBuffer<NetworkMetricsSnapshot> mNetworkMetricsSnapshots =
+ new RingBuffer<>(NetworkMetricsSnapshot.class, METRICS_SNAPSHOT_BUFFER_SIZE);
+ @GuardedBy("this")
+ private long mLastSnapshot = 0;
// Array of aggregated wakeup event stats, grouped by interface name.
@GuardedBy("this")
@@ -84,7 +91,7 @@
// Ring buffer array for storing packet wake up events sent by Netd.
@GuardedBy("this")
private final RingBuffer<WakeupEvent> mWakeupEvents =
- new RingBuffer(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH);
+ new RingBuffer<>(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH);
private final ConnectivityManager mCm;
@@ -116,6 +123,41 @@
mCm = cm;
}
+ private static long projectSnapshotTime(long timeMs) {
+ return (timeMs / METRICS_SNAPSHOT_SPAN_MS) * METRICS_SNAPSHOT_SPAN_MS;
+ }
+
+ private NetworkMetrics getMetricsForNetwork(long timeMs, int netId) {
+ collectPendingMetricsSnapshot(timeMs);
+ NetworkMetrics metrics = mNetworkMetrics.get(netId);
+ if (metrics == null) {
+ // TODO: allow to change transport for a given netid.
+ metrics = new NetworkMetrics(netId, getTransports(netId), mConnectTb);
+ mNetworkMetrics.put(netId, metrics);
+ }
+ return metrics;
+ }
+
+ private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() {
+ collectPendingMetricsSnapshot(System.currentTimeMillis());
+ return mNetworkMetricsSnapshots.toArray();
+ }
+
+ private void collectPendingMetricsSnapshot(long timeMs) {
+ // Detects time differences larger than the snapshot collection period.
+ // This is robust against clock jumps and long inactivity periods.
+ if (Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) {
+ return;
+ }
+ mLastSnapshot = projectSnapshotTime(timeMs);
+ NetworkMetricsSnapshot snapshot =
+ NetworkMetricsSnapshot.collect(mLastSnapshot, mNetworkMetrics);
+ if (snapshot.stats.isEmpty()) {
+ return;
+ }
+ mNetworkMetricsSnapshots.append(snapshot);
+ }
+
@Override
// Called concurrently by multiple binder threads.
// This method must not block or perform long-running operations.
@@ -124,15 +166,10 @@
throws RemoteException {
maybeVerboseLog("onDnsEvent(%d, %d, %d, %dms)", netId, eventType, returnCode, latencyMs);
- DnsEvent dnsEvent = mDnsEvents.get(netId);
- if (dnsEvent == null) {
- dnsEvent = makeDnsEvent(netId);
- mDnsEvents.put(netId, dnsEvent);
- }
- dnsEvent.addResult((byte) eventType, (byte) returnCode, latencyMs);
+ long timestamp = System.currentTimeMillis();
+ getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs);
if (mNetdEventCallback != null) {
- long timestamp = System.currentTimeMillis();
mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
}
}
@@ -144,15 +181,11 @@
int port, int uid) throws RemoteException {
maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs);
- ConnectStats connectStats = mConnectEvents.get(netId);
- if (connectStats == null) {
- connectStats = makeConnectStats(netId);
- mConnectEvents.put(netId, connectStats);
- }
- connectStats.addEvent(error, latencyMs, ipAddr);
+ long timestamp = System.currentTimeMillis();
+ getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr);
if (mNetdEventCallback != null) {
- mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid);
+ mNetdEventCallback.onConnectEvent(ipAddr, port, timestamp, uid);
}
}
@@ -189,11 +222,24 @@
}
public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
- flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
- flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
+ for (int i = 0; i < mNetworkMetrics.size(); i++) {
+ ConnectStats stats = mNetworkMetrics.valueAt(i).connectMetrics;
+ if (stats.eventCount == 0) {
+ continue;
+ }
+ events.add(IpConnectivityEventBuilder.toProto(stats));
+ }
+ for (int i = 0; i < mNetworkMetrics.size(); i++) {
+ DnsEvent ev = mNetworkMetrics.valueAt(i).dnsMetrics;
+ if (ev.eventCount == 0) {
+ continue;
+ }
+ events.add(IpConnectivityEventBuilder.toProto(ev));
+ }
for (int i = 0; i < mWakeupStats.size(); i++) {
events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
}
+ mNetworkMetrics.clear();
mWakeupStats.clear();
}
@@ -206,8 +252,15 @@
}
public synchronized void list(PrintWriter pw) {
- listEvents(pw, mConnectEvents, (x) -> x, "\n");
- listEvents(pw, mDnsEvents, (x) -> x, "\n");
+ for (int i = 0; i < mNetworkMetrics.size(); i++) {
+ pw.println(mNetworkMetrics.valueAt(i).connectMetrics);
+ }
+ for (int i = 0; i < mNetworkMetrics.size(); i++) {
+ pw.println(mNetworkMetrics.valueAt(i).dnsMetrics);
+ }
+ for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) {
+ pw.println(s);
+ }
for (int i = 0; i < mWakeupStats.size(); i++) {
pw.println(mWakeupStats.valueAt(i));
}
@@ -217,41 +270,17 @@
}
public synchronized void listAsProtos(PrintWriter pw) {
- listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto, "");
- listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto, "");
+ for (int i = 0; i < mNetworkMetrics.size(); i++) {
+ pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics));
+ }
+ for (int i = 0; i < mNetworkMetrics.size(); i++) {
+ pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics));
+ }
for (int i = 0; i < mWakeupStats.size(); i++) {
pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
}
}
- private static <T> void flushProtos(List<IpConnectivityEvent> out, SparseArray<T> in,
- Function<T, IpConnectivityEvent> mapper) {
- for (int i = 0; i < in.size(); i++) {
- out.add(mapper.apply(in.valueAt(i)));
- }
- in.clear();
- }
-
- private static <T> void listEvents(
- PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper, String separator) {
- // Proto derived Classes have toString method that adds a \n at the end.
- // Let the caller control that by passing in the line separator explicitly.
- for (int i = 0; i < events.size(); i++) {
- pw.print(mapper.apply(events.valueAt(i)));
- pw.print(separator);
- }
- }
-
- private ConnectStats makeConnectStats(int netId) {
- long transports = getTransports(netId);
- return new ConnectStats(netId, transports, mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS);
- }
-
- private DnsEvent makeDnsEvent(int netId) {
- long transports = getTransports(netId);
- return new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
- }
-
private long getTransports(int netId) {
// TODO: directly query ConnectivityService instead of going through Binder interface.
NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId));
@@ -268,4 +297,32 @@
private static void maybeVerboseLog(String s, Object... args) {
if (VDBG) Log.d(TAG, String.format(s, args));
}
+
+ /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
+ static class NetworkMetricsSnapshot {
+
+ public long timeMs;
+ public List<NetworkMetrics.Summary> stats = new ArrayList<>();
+
+ static NetworkMetricsSnapshot collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics) {
+ NetworkMetricsSnapshot snapshot = new NetworkMetricsSnapshot();
+ snapshot.timeMs = timeMs;
+ for (int i = 0; i < networkMetrics.size(); i++) {
+ NetworkMetrics.Summary s = networkMetrics.valueAt(i).getPendingStats();
+ if (s != null) {
+ snapshot.stats.add(s);
+ }
+ }
+ return snapshot;
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner j = new StringJoiner(", ");
+ for (NetworkMetrics.Summary s : stats) {
+ j.add(s.toString());
+ }
+ return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString());
+ }
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index d7cd81f..59870cb 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -28,6 +28,7 @@
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_UNSPECIFIED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static com.android.server.ConnectivityService.SHORT_ARG;
import android.app.Notification;
@@ -60,6 +61,7 @@
import android.net.RouteInfo;
import android.net.util.PrefixUtils;
import android.net.util.SharedLog;
+import android.net.util.VersionedBroadcastListener;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Bundle;
@@ -68,6 +70,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
@@ -184,6 +187,8 @@
// TODO: Figure out how to merge this and other downstream-tracking objects
// into a single coherent structure.
private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams;
+ private final VersionedBroadcastListener mCarrierConfigChange;
+ // TODO: Delete SimChangeListener; it's obsolete.
private final SimChangeListener mSimChange;
private volatile TetheringConfiguration mConfig;
@@ -224,11 +229,26 @@
mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
mForwardedDownstreams = new HashSet<>();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
+ mCarrierConfigChange = new VersionedBroadcastListener(
+ "CarrierConfigChangeListener", mContext, smHandler, filter,
+ (Intent ignored) -> {
+ mLog.log("OBSERVED carrier config change");
+ reevaluateSimCardProvisioning();
+ });
+ // TODO: Remove SimChangeListener altogether. For now, we retain it
+ // for logging purposes in case we need to debug something that might
+ // be related to changing signals from ACTION_SIM_STATE_CHANGED to
+ // ACTION_CARRIER_CONFIG_CHANGED.
mSimChange = new SimChangeListener(
- mContext, smHandler, () -> reevaluateSimCardProvisioning());
+ mContext, smHandler, () -> {
+ mLog.log("OBSERVED SIM card change");
+ });
mStateReceiver = new StateReceiver();
- IntentFilter filter = new IntentFilter();
+ filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_STATE);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
@@ -364,18 +384,30 @@
return false;
}
+ if (carrierConfigAffirmsEntitlementCheckNotRequired()) {
+ return false;
+ }
+ return (provisionApp.length == 2);
+ }
+
+ // The logic here is aimed solely at confirming that a CarrierConfig exists
+ // and affirms that entitlement checks are not required.
+ //
+ // TODO: find a better way to express this, or alter the checking process
+ // entirely so that this is more intuitive.
+ private boolean carrierConfigAffirmsEntitlementCheckNotRequired() {
// Check carrier config for entitlement checks
final CarrierConfigManager configManager = (CarrierConfigManager) mContext
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- if (configManager != null && configManager.getConfig() != null) {
- // we do have a CarrierConfigManager and it has a config.
- boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean(
- CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
- if (!isEntitlementCheckRequired) {
- return false;
- }
- }
- return (provisionApp.length == 2);
+ if (configManager == null) return false;
+
+ final PersistableBundle carrierConfig = configManager.getConfig();
+ if (carrierConfig == null) return false;
+
+ // A CarrierConfigManager was found and it has a config.
+ final boolean isEntitlementCheckRequired = carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
+ return !isEntitlementCheckRequired;
}
// Used by the SIM card change observation code.
@@ -818,6 +850,7 @@
} else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
handleWifiApAction(intent);
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+ mLog.log("OBSERVED configuration changed");
updateConfiguration();
}
}
@@ -1192,6 +1225,7 @@
private void reevaluateSimCardProvisioning() {
if (!hasMobileHotspotProvisionApp()) return;
+ if (carrierConfigAffirmsEntitlementCheckNotRequired()) return;
ArrayList<Integer> tethered = new ArrayList<>();
synchronized (mPublicSync) {
@@ -1559,6 +1593,7 @@
return;
}
+ mCarrierConfigChange.startListening();
mSimChange.startListening();
mUpstreamNetworkMonitor.start();
@@ -1576,6 +1611,7 @@
mOffload.stop();
mUpstreamNetworkMonitor.stop();
mSimChange.stopListening();
+ mCarrierConfigChange.stopListening();
notifyDownstreamsOfNewUpstreamIface(null);
handleNewUpstreamNetworkState(null);
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
index 3e60f9f..33c9355 100644
--- a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
+++ b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
@@ -23,12 +23,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.net.util.VersionedBroadcastListener;
+import android.net.util.VersionedBroadcastListener.IntentCallback;
import android.os.Handler;
import android.util.Log;
import com.android.internal.telephony.TelephonyIntents;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
/**
@@ -37,88 +40,40 @@
*
* @hide
*/
-public class SimChangeListener {
+public class SimChangeListener extends VersionedBroadcastListener {
private static final String TAG = SimChangeListener.class.getSimpleName();
private static final boolean DBG = false;
- private final Context mContext;
- private final Handler mTarget;
- private final AtomicInteger mSimBcastGenerationNumber;
- private final Runnable mCallback;
- private BroadcastReceiver mBroadcastReceiver;
-
public SimChangeListener(Context ctx, Handler handler, Runnable onSimCardLoadedCallback) {
- mContext = ctx;
- mTarget = handler;
- mCallback = onSimCardLoadedCallback;
- mSimBcastGenerationNumber = new AtomicInteger(0);
+ super(TAG, ctx, handler, makeIntentFilter(), makeCallback(onSimCardLoadedCallback));
}
- public int generationNumber() {
- return mSimBcastGenerationNumber.get();
- }
-
- public void startListening() {
- if (DBG) Log.d(TAG, "startListening for SIM changes");
-
- if (mBroadcastReceiver != null) return;
-
- mBroadcastReceiver = new SimChangeBroadcastReceiver(
- mSimBcastGenerationNumber.incrementAndGet());
+ private static IntentFilter makeIntentFilter() {
final IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-
- mContext.registerReceiver(mBroadcastReceiver, filter, null, mTarget);
+ return filter;
}
- public void stopListening() {
- if (DBG) Log.d(TAG, "stopListening for SIM changes");
+ private static Consumer<Intent> makeCallback(Runnable onSimCardLoadedCallback) {
+ return new Consumer<Intent>() {
+ private boolean mSimNotLoadedSeen = false;
- if (mBroadcastReceiver == null) return;
+ @Override
+ public void accept(Intent intent) {
+ final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
+ Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
+ mSimNotLoadedSeen);
- mSimBcastGenerationNumber.incrementAndGet();
- mContext.unregisterReceiver(mBroadcastReceiver);
- mBroadcastReceiver = null;
- }
+ if (!INTENT_VALUE_ICC_LOADED.equals(state)) {
+ mSimNotLoadedSeen = true;
+ return;
+ }
- private boolean isSimCardLoaded(String state) {
- return INTENT_VALUE_ICC_LOADED.equals(state);
- }
-
- private class SimChangeBroadcastReceiver extends BroadcastReceiver {
- // used to verify this receiver is still current
- final private int mGenerationNumber;
-
- // used to check the sim state transition from non-loaded to loaded
- private boolean mSimNotLoadedSeen = false;
-
- public SimChangeBroadcastReceiver(int generationNumber) {
- mGenerationNumber = generationNumber;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final int currentGenerationNumber = mSimBcastGenerationNumber.get();
-
- if (DBG) {
- Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
- ", current generationNumber=" + currentGenerationNumber);
+ if (mSimNotLoadedSeen) {
+ mSimNotLoadedSeen = false;
+ onSimCardLoadedCallback.run();
+ }
}
- if (mGenerationNumber != currentGenerationNumber) return;
-
- final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
- Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
- mSimNotLoadedSeen);
-
- if (!isSimCardLoaded(state)) {
- mSimNotLoadedSeen = true;
- return;
- }
-
- if (mSimNotLoadedSeen) {
- mSimNotLoadedSeen = false;
- mCallback.run();
- }
- }
+ };
}
}
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 6e1c21e..c4e6ff6 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -163,10 +163,6 @@
};
private SyncManager getSyncManager() {
- if (SystemProperties.getBoolean("config.disable_network", false)) {
- return null;
- }
-
synchronized(mSyncManagerLock) {
try {
// Try to create the SyncManager, return null if it fails (e.g. the disk is full).
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 2541050..c2167eb 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -585,9 +585,11 @@
} else {
flags = SurfaceControl.OPAQUE | SurfaceControl.HIDDEN;
}
- mSurfaceControl = new SurfaceControl(mSurfaceSession,
- "ColorFade", mDisplayWidth, mDisplayHeight,
- PixelFormat.OPAQUE, flags);
+ mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setName("ColorFade")
+ .setSize(mDisplayWidth, mDisplayHeight)
+ .setFlags(flags)
+ .build();
} catch (OutOfResourcesException ex) {
Slog.e(TAG, "Unable to create surface.", ex);
return false;
diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java
index dbbb318..bef6898 100644
--- a/services/core/java/com/android/server/display/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/DisplayTransformManager.java
@@ -16,15 +16,20 @@
package com.android.server.display;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
import android.opengl.Matrix;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.NightDisplayController;
import java.util.Arrays;
/**
@@ -34,6 +39,8 @@
private static final String TAG = "DisplayTransformManager";
+ private static final String SURFACE_FLINGER = "SurfaceFlinger";
+
/**
* Color transform level used by Night display to tint the display red.
*/
@@ -50,6 +57,15 @@
private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015;
private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014;
+ private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation";
+ private static final String PERSISTENT_PROPERTY_NATIVE_MODE = "persist.sys.sf.native_mode";
+
+ private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022;
+ private static final int SURFACE_FLINGER_TRANSACTION_NATIVE_MODE = 1023;
+
+ private static final float COLOR_SATURATION_NATURAL = 1.0f;
+ private static final float COLOR_SATURATION_BOOSTED = 1.1f;
+
/**
* Map of level -> color transformation matrix.
*/
@@ -161,7 +177,7 @@
* Propagates the provided color transformation matrix to the SurfaceFlinger.
*/
private static void applyColorMatrix(float[] m) {
- final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+ final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
if (flinger != null) {
final Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
@@ -187,7 +203,7 @@
* Propagates the provided Daltonization mode to the SurfaceFlinger.
*/
private static void applyDaltonizerMode(int mode) {
- final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+ final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
if (flinger != null) {
final Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
@@ -201,4 +217,73 @@
}
}
}
+
+ public static boolean isNativeModeEnabled() {
+ return SystemProperties.getBoolean(PERSISTENT_PROPERTY_NATIVE_MODE, false);
+ }
+
+ public boolean setColorMode(int colorMode) {
+ if (colorMode == NightDisplayController.COLOR_MODE_NATURAL) {
+ applySaturation(COLOR_SATURATION_NATURAL);
+ setNativeMode(false);
+ } else if (colorMode == NightDisplayController.COLOR_MODE_BOOSTED) {
+ applySaturation(COLOR_SATURATION_BOOSTED);
+ setNativeMode(false);
+ } else if (colorMode == NightDisplayController.COLOR_MODE_SATURATED) {
+ applySaturation(COLOR_SATURATION_NATURAL);
+ setNativeMode(true);
+ }
+
+ updateConfiguration();
+
+ return true;
+ }
+
+ /**
+ * Propagates the provided saturation to the SurfaceFlinger.
+ */
+ private void applySaturation(float saturation) {
+ SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, Float.toString(saturation));
+ final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
+ if (flinger != null) {
+ final Parcel data = Parcel.obtain();
+ data.writeInterfaceToken("android.ui.ISurfaceComposer");
+ data.writeFloat(saturation);
+ try {
+ flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to set saturation", ex);
+ } finally {
+ data.recycle();
+ }
+ }
+ }
+
+ /**
+ * Toggles native mode on/off in SurfaceFlinger.
+ */
+ private void setNativeMode(boolean enabled) {
+ SystemProperties.set(PERSISTENT_PROPERTY_NATIVE_MODE, enabled ? "1" : "0");
+ final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
+ if (flinger != null) {
+ final Parcel data = Parcel.obtain();
+ data.writeInterfaceToken("android.ui.ISurfaceComposer");
+ data.writeInt(enabled ? 1 : 0);
+ try {
+ flinger.transact(SURFACE_FLINGER_TRANSACTION_NATIVE_MODE, data, null, 0);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to set native mode", ex);
+ } finally {
+ data.recycle();
+ }
+ }
+ }
+
+ private void updateConfiguration() {
+ try {
+ ActivityManager.getService().updateConfiguration(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not update configuration", e);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index 9cf1367..a7c3ff9 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -52,7 +52,8 @@
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.TimeZone;
+
+import com.android.internal.R;
import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
@@ -126,12 +127,6 @@
public NightDisplayService(Context context) {
super(context);
mHandler = new Handler(Looper.getMainLooper());
-
- final String[] coefficients = context.getResources().getStringArray(
- com.android.internal.R.array.config_nightDisplayColorTemperatureCoefficients);
- for (int i = 0; i < 9 && i < coefficients.length; i++) {
- mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
- }
}
@Override
@@ -236,6 +231,8 @@
mController = new NightDisplayController(getContext(), mCurrentUser);
mController.setListener(this);
+ setCoefficientMatrix(getContext());
+
// Prepare color transformation matrix.
setMatrix(mController.getColorTemperature(), mMatrixNight);
@@ -331,6 +328,26 @@
applyTint(true);
}
+ @Override
+ public void onDisplayColorModeChanged(int colorMode) {
+ final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+ dtm.setColorMode(colorMode);
+
+ setCoefficientMatrix(getContext());
+ setMatrix(mController.getColorTemperature(), mMatrixNight);
+ applyTint(true);
+ }
+
+ private void setCoefficientMatrix(Context context) {
+ final boolean isNative = DisplayTransformManager.isNativeModeEnabled();
+ final String[] coefficients = context.getResources().getStringArray(isNative
+ ? R.array.config_nightDisplayColorTemperatureCoefficientsNative
+ : R.array.config_nightDisplayColorTemperatureCoefficients);
+ for (int i = 0; i < 9 && i < coefficients.length; i++) {
+ mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
+ }
+ }
+
/**
* Applies current color temperature matrix, or removes it if deactivated.
*
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 1df9c86..d0d951b 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -59,9 +59,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.security.KeyStore;
-import android.service.fingerprint.FingerprintActionStatsProto;
-import android.service.fingerprint.FingerprintServiceDumpProto;
-import android.service.fingerprint.FingerprintUserStatsProto;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -1374,11 +1371,11 @@
final PerformanceStats normal = mPerformanceMap.get(userId);
if (normal != null) {
final long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
- proto.write(FingerprintActionStatsProto.ACCEPT, normal.accept);
- proto.write(FingerprintActionStatsProto.REJECT, normal.reject);
- proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire);
- proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout);
- proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, normal.permanentLockout);
+ proto.write(PerformanceStatsProto.ACCEPT, normal.accept);
+ proto.write(PerformanceStatsProto.REJECT, normal.reject);
+ proto.write(PerformanceStatsProto.ACQUIRE, normal.acquire);
+ proto.write(PerformanceStatsProto.LOCKOUT, normal.lockout);
+ proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, normal.permanentLockout);
proto.end(countsToken);
}
@@ -1387,11 +1384,11 @@
final PerformanceStats crypto = mCryptoPerformanceMap.get(userId);
if (crypto != null) {
final long countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
- proto.write(FingerprintActionStatsProto.ACCEPT, crypto.accept);
- proto.write(FingerprintActionStatsProto.REJECT, crypto.reject);
- proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire);
- proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout);
- proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, crypto.permanentLockout);
+ proto.write(PerformanceStatsProto.ACCEPT, crypto.accept);
+ proto.write(PerformanceStatsProto.REJECT, crypto.reject);
+ proto.write(PerformanceStatsProto.ACQUIRE, crypto.acquire);
+ proto.write(PerformanceStatsProto.LOCKOUT, crypto.lockout);
+ proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, crypto.permanentLockout);
proto.end(countsToken);
}
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index d3fd3a9..a7e674b 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -65,7 +65,7 @@
private static final boolean DEBUG = JobSchedulerService.DEBUG;
private static final String TAG = "JobServiceContext";
/** Amount of time a job is allowed to execute for before being considered timed-out. */
- private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins.
+ public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins.
/** Amount of time the JobScheduler waits for the initial service launch+bind. */
private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000;
/** Amount of time the JobScheduler will wait for a response from an app for a message. */
@@ -216,7 +216,7 @@
final JobInfo ji = job.getJob();
mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
- isDeadlineExpired, triggeredUris, triggeredAuthorities);
+ isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
// Once we'e begun executing a job, we by definition no longer care whether
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 78367fe..ddee345 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -25,13 +25,16 @@
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkPolicyManager;
+import android.net.TrafficStats;
import android.os.Process;
import android.os.UserHandle;
+import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobServiceContext;
import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
@@ -99,18 +102,66 @@
}
}
+ /**
+ * Test to see if running the given job on the given network is sane.
+ * <p>
+ * For example, if a job is trying to send 10MB over a 128Kbps EDGE
+ * connection, it would take 10.4 minutes, and has no chance of succeeding
+ * before the job times out, so we'd be insane to try running it.
+ */
+ private boolean isSane(JobStatus jobStatus, NetworkCapabilities capabilities) {
+ final long estimatedBytes = jobStatus.getEstimatedNetworkBytes();
+ if (estimatedBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+ // We don't know how large the job is; cross our fingers!
+ return true;
+ }
+ if (capabilities == null) {
+ // We don't know what the network is like; cross our fingers!
+ return true;
+ }
+
+ // We don't ask developers to differentiate between upstream/downstream
+ // in their size estimates, so test against the slowest link direction.
+ final long downstream = capabilities.getLinkDownstreamBandwidthKbps();
+ final long upstream = capabilities.getLinkUpstreamBandwidthKbps();
+ final long slowest;
+ if (downstream > 0 && upstream > 0) {
+ slowest = Math.min(downstream, upstream);
+ } else if (downstream > 0) {
+ slowest = downstream;
+ } else if (upstream > 0) {
+ slowest = upstream;
+ } else {
+ // We don't know what the network is like; cross our fingers!
+ return true;
+ }
+
+ final long estimatedMillis = ((estimatedBytes * DateUtils.SECOND_IN_MILLIS)
+ / (slowest * TrafficStats.KB_IN_BYTES / 8));
+ if (estimatedMillis > JobServiceContext.EXECUTING_TIMESLICE_MILLIS) {
+ // If we'd never finish before the timeout, we'd be insane!
+ Slog.w(TAG, "Estimated " + estimatedBytes + " bytes over " + slowest
+ + " kbps network would take " + estimatedMillis + "ms; that's insane!");
+ return false;
+ } else {
+ return true;
+ }
+ }
+
private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
final int jobUid = jobStatus.getSourceUid();
final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
- final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobUid, ignoreBlocked);
final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
+ final NetworkInfo info = mConnManager.getNetworkInfoForUid(network, jobUid, ignoreBlocked);
+
final NetworkCapabilities capabilities = (network != null)
? mConnManager.getNetworkCapabilities(network) : null;
+ final boolean connected = (info != null) && info.isConnected();
final boolean validated = (capabilities != null)
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
- final boolean connected = (info != null) && info.isConnected();
- final boolean connectionUsable = connected && validated;
+ final boolean sane = isSane(jobStatus, capabilities);
+ final boolean connectionUsable = connected && validated && sane;
final boolean metered = connected && (capabilities != null)
&& !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
@@ -125,6 +176,11 @@
changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered);
changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming);
+ // Pass along the evaluated network for job to use; prevents race
+ // conditions as default routes change over time, and opens the door to
+ // using non-default routes.
+ jobStatus.network = network;
+
// Track system-uid connected/validated as a general reportable proxy for the
// overall state of connectivity constraint satisfiability.
if (jobUid == Process.SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 23caa8c..46ed84e 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -22,6 +22,7 @@
import android.app.job.JobWorkItem;
import android.content.ClipData;
import android.content.ComponentName;
+import android.net.Network;
import android.net.Uri;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -167,6 +168,7 @@
// These are filled in by controllers when preparing for execution.
public ArraySet<Uri> changedUris;
public ArraySet<String> changedAuthorities;
+ public Network network;
public int lastEvaluatedPriority;
@@ -217,6 +219,8 @@
*/
ContentObserverController.JobInstance contentObserverJobInstance;
+ private long totalNetworkBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+
/** Provide a handle to the service that this job will be run on. */
public int getServiceToken() {
return callingUid;
@@ -294,6 +298,8 @@
mLastSuccessfulRunTime = lastSuccessfulRunTime;
mLastFailedRunTime = lastFailedRunTime;
+
+ updateEstimatedNetworkBytesLocked();
}
/** Copy constructor: used specifically when cloning JobStatus objects for persistence,
@@ -387,6 +393,7 @@
sourcePackageName, sourceUserId, toShortString()));
}
pendingWork.add(work);
+ updateEstimatedNetworkBytesLocked();
}
public JobWorkItem dequeueWorkLocked() {
@@ -399,6 +406,7 @@
executingWork.add(work);
work.bumpDeliveryCount();
}
+ updateEstimatedNetworkBytesLocked();
return work;
}
return null;
@@ -457,6 +465,7 @@
pendingWork = null;
executingWork = null;
incomingJob.nextPendingWorkId = nextPendingWorkId;
+ incomingJob.updateEstimatedNetworkBytesLocked();
} else {
// We are completely stopping the job... need to clean up work.
ungrantWorkList(am, pendingWork);
@@ -464,6 +473,7 @@
ungrantWorkList(am, executingWork);
executingWork = null;
}
+ updateEstimatedNetworkBytesLocked();
}
public void prepareLocked(IActivityManager am) {
@@ -566,6 +576,38 @@
return job.getFlags();
}
+ private void updateEstimatedNetworkBytesLocked() {
+ totalNetworkBytes = computeEstimatedNetworkBytesLocked();
+ }
+
+ private long computeEstimatedNetworkBytesLocked() {
+ // If any component of the job has unknown usage, we don't have a
+ // complete picture of what data will be used, and we have to treat the
+ // entire job as unknown.
+ long totalNetworkBytes = 0;
+ long networkBytes = job.getEstimatedNetworkBytes();
+ if (networkBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+ return JobInfo.NETWORK_BYTES_UNKNOWN;
+ } else {
+ totalNetworkBytes += networkBytes;
+ }
+ if (pendingWork != null) {
+ for (int i = 0; i < pendingWork.size(); i++) {
+ networkBytes = pendingWork.get(i).getEstimatedNetworkBytes();
+ if (networkBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+ return JobInfo.NETWORK_BYTES_UNKNOWN;
+ } else {
+ totalNetworkBytes += networkBytes;
+ }
+ }
+ }
+ return totalNetworkBytes;
+ }
+
+ public long getEstimatedNetworkBytes() {
+ return totalNetworkBytes;
+ }
+
/** Does this job have any sort of networking constraint? */
public boolean hasConnectivityConstraint() {
return (requiredConstraints&CONNECTIVITY_MASK) != 0;
@@ -1045,6 +1087,9 @@
if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
pw.print(prefix); pw.print(" Network type: "); pw.println(job.getNetworkType());
}
+ if (totalNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ pw.print(prefix); pw.print(" Network bytes: "); pw.println(totalNetworkBytes);
+ }
if (job.getMinLatencyMillis() != 0) {
pw.print(prefix); pw.print(" Minimum latency: ");
TimeUtils.formatDuration(job.getMinLatencyMillis(), pw);
@@ -1101,6 +1146,9 @@
}
}
}
+ if (network != null) {
+ pw.print(prefix); pw.print("Network: "); pw.println(network);
+ }
if (pendingWork != null && pendingWork.size() > 0) {
pw.print(prefix); pw.println("Pending work:");
for (int i = 0; i < pendingWork.size(); i++) {
diff --git a/services/core/java/com/android/server/location/GeofenceManager.java b/services/core/java/com/android/server/location/GeofenceManager.java
index 2493dfb..d50ffe9 100644
--- a/services/core/java/com/android/server/location/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/GeofenceManager.java
@@ -145,7 +145,8 @@
*/
private void updateMinInterval() {
mEffectiveMinIntervalMs = Settings.Global.getLong(mResolver,
- Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, DEFAULT_MIN_INTERVAL_MS);
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
+ DEFAULT_MIN_INTERVAL_MS);
}
public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index e41c17d..660659f 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -669,9 +669,11 @@
for (String item : configValues) {
if (DEBUG) Log.d(TAG, "GpsParamsResource: " + item);
// We need to support "KEY =", but not "=VALUE".
- String[] split = item.split("=");
- if (split.length == 2) {
- properties.setProperty(split[0].trim().toUpperCase(), split[1]);
+ int index = item.indexOf("=");
+ if (index > 0 && index + 1 < item.length()) {
+ String key = item.substring(0, index);
+ String value = item.substring(index + 1);
+ properties.setProperty(key.trim().toUpperCase(), value);
} else {
Log.w(TAG, "malformed contents: " + item);
}
@@ -801,18 +803,6 @@
}
};
mGnssMetrics = new GnssMetrics();
-
- /*
- * A cycle of native_init() and native_cleanup() is needed so that callbacks are registered
- * after bootup even when location is disabled. This will allow Emergency SUPL to work even
- * when location is disabled before device restart.
- * */
- boolean isInitialized = native_init();
- if(!isInitialized) {
- Log.d(TAG, "Failed to initialize at bootup");
- } else {
- native_cleanup();
- }
}
/**
@@ -2270,6 +2260,19 @@
* this handler.
*/
private void handleInitialize() {
+ /*
+ * A cycle of native_init() and native_cleanup() is needed so that callbacks are
+ * registered after bootup even when location is disabled.
+ * This will allow Emergency SUPL to work even when location is disabled before device
+ * restart.
+ */
+ boolean isInitialized = native_init();
+ if(!isInitialized) {
+ Log.w(TAG, "Native initialization failed at bootup");
+ } else {
+ native_cleanup();
+ }
+
// load default GPS configuration
// (this configuration might change in the future based on SIM changes)
reloadGpsProperties(mContext, mProperties);
diff --git a/services/core/java/com/android/server/net/OWNERS b/services/core/java/com/android/server/net/OWNERS
index 061fd8d..6b77d83 100644
--- a/services/core/java/com/android/server/net/OWNERS
+++ b/services/core/java/com/android/server/net/OWNERS
@@ -2,7 +2,7 @@
ek@google.com
hugobenichi@google.com
-jsharkey@google.com
+jsharkey@android.com
lorenzo@google.com
satk@google.com
silberst@google.com
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 238d87b..6cebdd6 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -460,7 +460,7 @@
mRankingHelper.readXml(parser, forRestore);
}
// No non-system managed services are allowed on low ram devices
- if (!ActivityManager.isLowRamDeviceStatic()) {
+ if (canUseManagedServices()) {
if (mListeners.getConfig().xmlTag.equals(parser.getName())) {
mListeners.readXml(parser);
migratedManagedServices = true;
@@ -548,12 +548,6 @@
out.endDocument();
}
- /** Use this to check if a package can post a notification or toast. */
- private boolean checkNotificationOp(String pkg, int uid) {
- return mAppOps.checkOp(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
- == AppOpsManager.MODE_ALLOWED && !isPackageSuspendedForUser(pkg, uid);
- }
-
private static final class ToastRecord
{
final int pid;
@@ -1226,7 +1220,6 @@
mAccessibilityManager = am;
}
-
// TODO: All tests should use this init instead of the one-off setters above.
@VisibleForTesting
void init(Looper looper, IPackageManager packageManager,
@@ -2818,19 +2811,25 @@
@Override
public void setNotificationPolicyAccessGranted(String pkg, boolean granted)
throws RemoteException {
+ setNotificationPolicyAccessGrantedForUser(
+ pkg, getCallingUserHandle().getIdentifier(), granted);
+ }
+
+ @Override
+ public void setNotificationPolicyAccessGrantedForUser(
+ String pkg, int userId, boolean granted) {
checkCallerIsSystemOrShell();
final long identity = Binder.clearCallingIdentity();
try {
- if (!mActivityManager.isLowRamDevice()) {
+ if (canUseManagedServices()) {
mConditionProviders.setPackageOrComponentEnabled(
- pkg, getCallingUserHandle().getIdentifier(), true, granted);
+ pkg, userId, true, granted);
getContext().sendBroadcastAsUser(new Intent(
NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
.setPackage(pkg)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
- getCallingUserHandle(), null);
-
+ UserHandle.of(userId), null);
savePolicyFile();
}
} finally {
@@ -2918,18 +2917,17 @@
checkCallerIsSystemOrShell();
final long identity = Binder.clearCallingIdentity();
try {
- if (!mActivityManager.isLowRamDevice()) {
+ if (canUseManagedServices()) {
mConditionProviders.setPackageOrComponentEnabled(listener.flattenToString(),
userId, false, granted);
mListeners.setPackageOrComponentEnabled(listener.flattenToString(),
userId, true, granted);
getContext().sendBroadcastAsUser(new Intent(
- NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
-
+ NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
.setPackage(listener.getPackageName())
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
- getCallingUserHandle(), null);
+ UserHandle.of(userId), null);
savePolicyFile();
}
@@ -2945,7 +2943,7 @@
checkCallerIsSystemOrShell();
final long identity = Binder.clearCallingIdentity();
try {
- if (!mActivityManager.isLowRamDevice()) {
+ if (canUseManagedServices()) {
mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(),
userId, false, granted);
mAssistants.setPackageOrComponentEnabled(assistant.flattenToString(),
@@ -2955,7 +2953,7 @@
NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
.setPackage(assistant.getPackageName())
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
- getCallingUserHandle(), null);
+ UserHandle.of(userId), null);
savePolicyFile();
}
@@ -5434,6 +5432,11 @@
}
}
+ private boolean canUseManagedServices() {
+ return !mActivityManager.isLowRamDevice()
+ || mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+
private class TrimCache {
StatusBarNotification heavy;
StatusBarNotification sbnClone;
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 6d8cac0..679250c 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -340,7 +340,8 @@
int dexoptFlags =
DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
DexoptOptions.DEXOPT_BOOT_COMPLETE |
- (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0);
+ (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) |
+ DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
if (is_for_primary_dex) {
int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
dexoptFlags));
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 371b3ef..210eb13 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -56,6 +56,8 @@
public static final int DEXOPT_STORAGE_CE = 1 << 7;
/** Indicates that the dex file passed to dexopt in on DE storage. */
public static final int DEXOPT_STORAGE_DE = 1 << 8;
+ /** Indicates that dexopt is invoked from the background service. */
+ public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
// NOTE: keep in sync with installd
public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 6253857..03f662a 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -310,7 +310,7 @@
collectingInstaller, mPackageManagerService.mInstallLock, mContext);
String[] libraryDependencies = pkg.usesLibraryFiles;
- if (pkg.isSystemApp()) {
+ if (pkg.isSystem()) {
// For system apps, we want to avoid classpaths checks.
libraryDependencies = NO_LIBRARIES;
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index cf0ffbb..50ac409 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -54,6 +54,7 @@
import static com.android.server.pm.Installer.DEXOPT_FORCE;
import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
+import static com.android.server.pm.Installer.DEXOPT_IDLE_BACKGROUND_JOB;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
@@ -110,7 +111,7 @@
}
// We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
- if (pkg.isPrivilegedApp()) {
+ if (pkg.isPrivileged()) {
return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
}
@@ -208,7 +209,7 @@
// Get the dexopt flags after getRealCompilerFilter to make sure we get the correct
// flags.
- final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete());
+ final int dexoptFlags = getDexFlags(pkg, compilerFilter, options);
for (String dexCodeIsa : dexCodeInstructionSets) {
int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter,
@@ -348,8 +349,7 @@
dexUseInfo.isUsedByOtherApps());
// Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
// Secondary dex files are currently not compiled at boot.
- int dexoptFlags = getDexFlags(info, compilerFilter, /* bootComplete */ true)
- | DEXOPT_SECONDARY_DEX;
+ int dexoptFlags = getDexFlags(info, compilerFilter, options) | DEXOPT_SECONDARY_DEX;
// Check the app storage and add the appropriate flags.
if (info.deviceProtectedDataDir != null &&
FileUtils.contains(info.deviceProtectedDataDir, path)) {
@@ -485,11 +485,11 @@
* filter.
*/
private int getDexFlags(PackageParser.Package pkg, String compilerFilter,
- boolean bootComplete) {
- return getDexFlags(pkg.applicationInfo, compilerFilter, bootComplete);
+ DexoptOptions options) {
+ return getDexFlags(pkg.applicationInfo, compilerFilter, options);
}
- private int getDexFlags(ApplicationInfo info, String compilerFilter, boolean bootComplete) {
+ private int getDexFlags(ApplicationInfo info, String compilerFilter, DexoptOptions options) {
int flags = info.flags;
boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
// Profile guide compiled oat files should not be public.
@@ -500,7 +500,8 @@
(isPublic ? DEXOPT_PUBLIC : 0)
| (debuggable ? DEXOPT_DEBUGGABLE : 0)
| profileFlag
- | (bootComplete ? DEXOPT_BOOTCOMPLETE : 0);
+ | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0)
+ | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0);
return adjustDexoptFlags(dexFlags);
}
@@ -612,6 +613,9 @@
if ((flags & DEXOPT_STORAGE_DE) == DEXOPT_STORAGE_DE) {
flagsList.add("storage_de");
}
+ if ((flags & DEXOPT_IDLE_BACKGROUND_JOB) == DEXOPT_IDLE_BACKGROUND_JOB) {
+ flagsList.add("idle_background_job");
+ }
return String.join(",", flagsList);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 391deb7..7837b02 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -397,7 +397,7 @@
static final boolean DEBUG_UPGRADE = false;
static final boolean DEBUG_DOMAIN_VERIFICATION = false;
private static final boolean DEBUG_BACKUP = false;
- private static final boolean DEBUG_INSTALL = false;
+ public static final boolean DEBUG_INSTALL = false;
public static final boolean DEBUG_REMOVE = false;
private static final boolean DEBUG_BROADCASTS = false;
private static final boolean DEBUG_SHOW_INFO = false;
@@ -522,7 +522,7 @@
*/
private static final int DEFAULT_VERIFICATION_RESPONSE = PackageManager.VERIFICATION_ALLOW;
- static final String PLATFORM_PACKAGE_NAME = "android";
+ public static final String PLATFORM_PACKAGE_NAME = "android";
static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
@@ -542,18 +542,6 @@
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
- /** Permission grant: not grant the permission. */
- private static final int GRANT_DENIED = 1;
-
- /** Permission grant: grant the permission as an install permission. */
- private static final int GRANT_INSTALL = 2;
-
- /** Permission grant: grant the permission as a runtime one. */
- private static final int GRANT_RUNTIME = 3;
-
- /** Permission grant: grant as runtime a permission that was granted as an install time one. */
- private static final int GRANT_UPGRADE = 4;
-
/** Canonical intent used to identify what counts as a "web browser" app */
private static final Intent sBrowserIntent;
static {
@@ -753,9 +741,6 @@
PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy;
- // System configuration read by SystemConfig.
- final int[] mGlobalGids;
- final SparseArray<ArraySet<String>> mSystemPermissions;
@GuardedBy("mAvailableFeatures")
final ArrayMap<String, FeatureInfo> mAvailableFeatures;
@@ -938,10 +923,6 @@
final ArrayMap<ComponentName, PackageParser.Instrumentation> mInstrumentation =
new ArrayMap<ComponentName, PackageParser.Instrumentation>();
- // Mapping from permission names to info about them.
- final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups =
- new ArrayMap<String, PackageParser.PermissionGroup>();
-
// Packages whose data we have transfered into another package, thus
// should no longer exist.
final ArraySet<String> mTransferedPackages = new ArraySet<String>();
@@ -1016,8 +997,6 @@
private File mCacheDir;
- private ArraySet<String> mPrivappPermissionsViolations;
-
private Future<?> mPrepareAppDataFuture;
private static class IFVerificationParams {
@@ -1405,8 +1384,6 @@
final @NonNull String mServicesSystemSharedLibraryPackageName;
final @NonNull String mSharedSystemSharedLibraryPackageName;
- final boolean mPermissionReviewRequired;
-
private final PackageUsage mPackageUsage = new PackageUsage();
private final CompilerStats mCompilerStats = new CompilerStats();
@@ -1928,9 +1905,11 @@
}
}
@Override
- public void onPermissionUpdated(int userId) {
+ public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
synchronized (mPackages) {
- mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+ for (int userId : updatedUserIds) {
+ mSettings.writeRuntimePermissionsForUserLPr(userId, sync);
+ }
}
}
@Override
@@ -2363,9 +2342,6 @@
mContext = context;
- mPermissionReviewRequired = context.getResources().getBoolean(
- R.bool.config_permissionReviewRequired);
-
mFactoryTest = factoryTest;
mOnlyCore = onlyCore;
mMetrics = new DisplayMetrics();
@@ -2434,8 +2410,6 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "get system config");
SystemConfig systemConfig = SystemConfig.getInstance();
- mGlobalGids = systemConfig.getGlobalGids();
- mSystemPermissions = systemConfig.getSystemPermissions();
mAvailableFeatures = systemConfig.getAvailableFeatures();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -2872,13 +2846,14 @@
// cases get permissions that the user didn't initially explicitly
// allow... it would be nice to have some better way to handle
// this situation.
- int updateFlags = UPDATE_PERMISSIONS_ALL;
- if (ver.sdkVersion != mSdkVersion) {
+ final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion);
+ if (sdkUpdated) {
Slog.i(TAG, "Platform changed from " + ver.sdkVersion + " to "
+ mSdkVersion + "; regranting permissions for internal storage");
- updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
}
- updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
+ mPermissionManager.updateAllPermissions(
+ StorageManager.UUID_PRIVATE_INTERNAL, sdkUpdated, mPackages.values(),
+ mPermissionCallback);
ver.sdkVersion = mSdkVersion;
// If this is the first boot or an update from pre-M, and it is a normal
@@ -3597,7 +3572,7 @@
for (String packageName : packages) {
PackageParser.Package pkg = mPackages.get(packageName);
if (pkg != null) {
- if (!pkg.isSystemApp()) {
+ if (!pkg.isSystem()) {
Slog.w(TAG, "Non-system app '" + packageName + "' in sysconfig <app-link>");
continue;
}
@@ -3744,19 +3719,16 @@
* <p>
* Currently, there are three cases in which this can occur:
* <ol>
- * <li>The calling application is a "special" process. The special
- * processes are {@link Process#SYSTEM_UID}, {@link Process#SHELL_UID}
- * and {@code 0}</li>
+ * <li>The calling application is a "special" process. Special processes
+ * are those with a UID < {@link Process#FIRST_APPLICATION_UID}.</li>
* <li>The calling application has the permission
- * {@link android.Manifest.permission#ACCESS_INSTANT_APPS}</li>
+ * {@link android.Manifest.permission#ACCESS_INSTANT_APPS}.</li>
* <li>The calling application is the default launcher on the
* system partition.</li>
* </ol>
*/
private boolean canViewInstantApps(int callingUid, int userId) {
- if (callingUid == Process.SYSTEM_UID
- || callingUid == Process.SHELL_UID
- || callingUid == Process.ROOT_UID) {
+ if (callingUid < Process.FIRST_APPLICATION_UID) {
return true;
}
if (mContext.checkCallingOrSelfPermission(
@@ -4228,44 +4200,22 @@
@Override
public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName,
int flags) {
- // TODO Move this to PermissionManager when mPermissionGroups is moved there
- synchronized (mPackages) {
- if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
- // This is thrown as NameNotFoundException
- return null;
- }
- }
- return new ParceledListSlice<>(
- mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid()));
+ final List<PermissionInfo> permissionList =
+ mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid());
+ return (permissionList == null) ? null : new ParceledListSlice<>(permissionList);
}
@Override
- public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return null;
- }
- // reader
- synchronized (mPackages) {
- return PackageParser.generatePermissionGroupInfo(
- mPermissionGroups.get(name), flags);
- }
+ public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) {
+ return mPermissionManager.getPermissionGroupInfo(groupName, flags, getCallingUid());
}
@Override
public @NonNull ParceledListSlice<PermissionGroupInfo> getAllPermissionGroups(int flags) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return ParceledListSlice.emptyList();
- }
- // reader
- synchronized (mPackages) {
- final int N = mPermissionGroups.size();
- ArrayList<PermissionGroupInfo> out
- = new ArrayList<PermissionGroupInfo>(N);
- for (PackageParser.PermissionGroup pg : mPermissionGroups.values()) {
- out.add(PackageParser.generatePermissionGroupInfo(pg, flags));
- }
- return new ParceledListSlice<>(out);
- }
+ final List<PermissionGroupInfo> permissionList =
+ mPermissionManager.getAllPermissionGroups(flags, getCallingUid());
+ return (permissionList == null)
+ ? ParceledListSlice.emptyList() : new ParceledListSlice<>(permissionList);
}
private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags,
@@ -5138,59 +5088,7 @@
@Override
public int checkUidPermission(String permName, int uid) {
- final int callingUid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(callingUid);
- final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
- final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
- final int userId = UserHandle.getUserId(uid);
- if (!sUserManager.exists(userId)) {
- return PackageManager.PERMISSION_DENIED;
- }
-
- synchronized (mPackages) {
- Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
- if (obj != null) {
- if (obj instanceof SharedUserSetting) {
- if (isCallerInstantApp) {
- return PackageManager.PERMISSION_DENIED;
- }
- } else if (obj instanceof PackageSetting) {
- final PackageSetting ps = (PackageSetting) obj;
- if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
- return PackageManager.PERMISSION_DENIED;
- }
- }
- final SettingBase settingBase = (SettingBase) obj;
- final PermissionsState permissionsState = settingBase.getPermissionsState();
- if (permissionsState.hasPermission(permName, userId)) {
- if (isUidInstantApp) {
- if (mSettings.mPermissions.isPermissionInstant(permName)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- } else {
- return PackageManager.PERMISSION_GRANTED;
- }
- }
- // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
- if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
- .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- } else {
- ArraySet<String> perms = mSystemPermissions.get(uid);
- if (perms != null) {
- if (perms.contains(permName)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
- .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- }
- }
- }
-
- return PackageManager.PERMISSION_DENIED;
+ return mPermissionManager.checkUidPermission(permName, uid, getCallingUid());
}
@Override
@@ -5352,7 +5250,9 @@
}
synchronized (mPackages) {
- updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL);
+ mPermissionManager.updateAllPermissions(
+ StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(),
+ mPermissionCallback);
for (int userId : UserManagerService.getInstance().getUserIds()) {
final int packageCount = mPackages.size();
for (int i = 0; i < packageCount; i++) {
@@ -5369,7 +5269,8 @@
@Override
public int getPermissionFlags(String permName, String packageName, int userId) {
- return mPermissionManager.getPermissionFlags(permName, packageName, getCallingUid(), userId);
+ return mPermissionManager.getPermissionFlags(
+ permName, packageName, getCallingUid(), userId);
}
@Override
@@ -8786,6 +8687,7 @@
pkg.applicationInfo.primaryCpuAbi = updatedPkg.primaryCpuAbiString;
pkg.applicationInfo.secondaryCpuAbi = updatedPkg.secondaryCpuAbiString;
}
+ pkg.mExtras = updatedPkg;
throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at "
+ scanFile + " ignored: updated version " + ps.versionCode
@@ -9914,7 +9816,7 @@
// it is better for the user to reinstall than to be in an limbo
// state. Also libs disappearing under an app should never happen
// - just in case.
- if (!pkg.isSystemApp() || pkg.isUpdatedSystemApp()) {
+ if (!pkg.isSystem() || pkg.isUpdatedSystemApp()) {
final int flags = pkg.isUpdatedSystemApp()
? PackageManager.DELETE_KEEP_DATA : 0;
deletePackageLIF(pkg.packageName, null, true, sUserManager.getUserIds(),
@@ -10063,7 +9965,7 @@
assertPackageIsValid(pkg, policyFlags, scanFlags);
if (Build.IS_DEBUGGABLE &&
- pkg.isPrivilegedApp() &&
+ pkg.isPrivileged() &&
!SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
}
@@ -11156,54 +11058,15 @@
if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Activities: " + r);
}
- N = pkg.permissionGroups.size();
- r = null;
- for (i=0; i<N; i++) {
- PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
- PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name);
- final String curPackageName = cur == null ? null : cur.info.packageName;
- // Dont allow ephemeral apps to define new permission groups.
- if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
- Slog.w(TAG, "Permission group " + pg.info.name + " from package "
- + pg.info.packageName
- + " ignored: instant apps cannot define new permission groups.");
- continue;
- }
- final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
- if (cur == null || isPackageUpdate) {
- mPermissionGroups.put(pg.info.name, pg);
- if (chatty) {
- if (r == null) {
- r = new StringBuilder(256);
- } else {
- r.append(' ');
- }
- if (isPackageUpdate) {
- r.append("UPD:");
- }
- r.append(pg.info.name);
- }
- } else {
- Slog.w(TAG, "Permission group " + pg.info.name + " from package "
- + pg.info.packageName + " ignored: original from "
- + cur.info.packageName);
- if (chatty) {
- if (r == null) {
- r = new StringBuilder(256);
- } else {
- r.append(' ');
- }
- r.append("DUP:");
- r.append(pg.info.name);
- }
- }
- }
- if (r != null) {
- if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permission Groups: " + r);
+ // Don't allow ephemeral applications to define new permissions groups.
+ if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
+ Slog.w(TAG, "Permission groups from package " + pkg.packageName
+ + " ignored: instant apps cannot define new permission groups.");
+ } else {
+ mPermissionManager.addAllPermissionGroups(pkg, chatty);
}
-
- // Dont allow ephemeral apps to define new permissions.
+ // Don't allow ephemeral applications to define new permissions.
if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
Slog.w(TAG, "Permissions from package " + pkg.packageName
+ " ignored: instant apps cannot define new permissions.");
@@ -11995,611 +11858,6 @@
}
}
- public static final int UPDATE_PERMISSIONS_ALL = 1<<0;
- public static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
- public static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
-
- private void updatePermissionsLPw(PackageParser.Package pkg, int flags) {
- // Update the parent permissions
- updatePermissionsLPw(pkg.packageName, pkg, flags);
- // Update the child permissions
- final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
- for (int i = 0; i < childCount; i++) {
- PackageParser.Package childPkg = pkg.childPackages.get(i);
- updatePermissionsLPw(childPkg.packageName, childPkg, flags);
- }
- }
-
- private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo,
- int flags) {
- final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null;
- updatePermissionsLocked(changingPkg, pkgInfo, volumeUuid, flags);
- }
-
- private void updatePermissionsLocked(String changingPkg,
- PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
- // TODO: Most of the methods exposing BasePermission internals [source package name,
- // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
- // have package settings, we should make note of it elsewhere [map between
- // source package name and BasePermission] and cycle through that here. Then we
- // define a single method on BasePermission that takes a PackageSetting, changing
- // package name and a package.
- // NOTE: With this approach, we also don't need to tree trees differently than
- // normal permissions. Today, we need two separate loops because these BasePermission
- // objects are stored separately.
- // Make sure there are no dangling permission trees.
- flags = mPermissionManager.updatePermissionTrees(changingPkg, pkgInfo, flags);
-
- // Make sure all dynamic permissions have been assigned to a package,
- // and make sure there are no dangling permissions.
- flags = mPermissionManager.updatePermissions(changingPkg, pkgInfo, flags);
-
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions");
- // Now update the permissions for all packages, in particular
- // replace the granted permissions of the system packages.
- if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
- for (PackageParser.Package pkg : mPackages.values()) {
- if (pkg != pkgInfo) {
- // Only replace for packages on requested volume
- final String volumeUuid = getVolumeUuidForPackage(pkg);
- final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
- && Objects.equals(replaceVolumeUuid, volumeUuid);
- grantPermissionsLPw(pkg, replace, changingPkg);
- }
- }
- }
-
- if (pkgInfo != null) {
- // Only replace for packages on requested volume
- final String volumeUuid = getVolumeUuidForPackage(pkgInfo);
- final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
- && Objects.equals(replaceVolumeUuid, volumeUuid);
- grantPermissionsLPw(pkgInfo, replace, changingPkg);
- }
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
-
- private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace,
- String packageOfInterest) {
- // IMPORTANT: There are two types of permissions: install and runtime.
- // Install time permissions are granted when the app is installed to
- // all device users and users added in the future. Runtime permissions
- // are granted at runtime explicitly to specific users. Normal and signature
- // protected permissions are install time permissions. Dangerous permissions
- // are install permissions if the app's target SDK is Lollipop MR1 or older,
- // otherwise they are runtime permissions. This function does not manage
- // runtime permissions except for the case an app targeting Lollipop MR1
- // being upgraded to target a newer SDK, in which case dangerous permissions
- // are transformed from install time to runtime ones.
-
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
- if (ps == null) {
- return;
- }
-
- PermissionsState permissionsState = ps.getPermissionsState();
- PermissionsState origPermissions = permissionsState;
-
- final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
-
- boolean runtimePermissionsRevoked = false;
- int[] changedRuntimePermissionUserIds = EMPTY_INT_ARRAY;
-
- boolean changedInstallPermission = false;
-
- if (replace) {
- ps.installPermissionsFixed = false;
- if (!ps.isSharedUser()) {
- origPermissions = new PermissionsState(permissionsState);
- permissionsState.reset();
- } else {
- // We need to know only about runtime permission changes since the
- // calling code always writes the install permissions state but
- // the runtime ones are written only if changed. The only cases of
- // changed runtime permissions here are promotion of an install to
- // runtime and revocation of a runtime from a shared user.
- changedRuntimePermissionUserIds =
- mPermissionManager.revokeUnusedSharedUserPermissions(
- ps.sharedUser, UserManagerService.getInstance().getUserIds());
- if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) {
- runtimePermissionsRevoked = true;
- }
- }
- }
-
- permissionsState.setGlobalGids(mGlobalGids);
-
- final int N = pkg.requestedPermissions.size();
- for (int i=0; i<N; i++) {
- final String name = pkg.requestedPermissions.get(i);
- final BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(name);
- final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
- >= Build.VERSION_CODES.M;
-
- if (DEBUG_INSTALL) {
- Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
- }
-
- if (bp == null || bp.getSourcePackageSetting() == null) {
- if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, "Unknown permission " + name
- + " in package " + pkg.packageName);
- }
- }
- continue;
- }
-
-
- // Limit ephemeral apps to ephemeral allowed permissions.
- if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
- if (DEBUG_PERMISSIONS) {
- Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + " for package "
- + pkg.packageName);
- }
- continue;
- }
-
- if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
- if (DEBUG_PERMISSIONS) {
- Log.i(TAG, "Denying runtime-only permission " + bp.getName() + " for package "
- + pkg.packageName);
- }
- continue;
- }
-
- final String perm = bp.getName();
- boolean allowedSig = false;
- int grant = GRANT_DENIED;
-
- // Keep track of app op permissions.
- if (bp.isAppOp()) {
- mSettings.addAppOpPackage(perm, pkg.packageName);
- }
-
- if (bp.isNormal()) {
- // For all apps normal permissions are install time ones.
- grant = GRANT_INSTALL;
- } else if (bp.isRuntime()) {
- // If a permission review is required for legacy apps we represent
- // their permissions as always granted runtime ones since we need
- // to keep the review required permission flag per user while an
- // install permission's state is shared across all users.
- if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) {
- // For legacy apps dangerous permissions are install time ones.
- grant = GRANT_INSTALL;
- } else if (origPermissions.hasInstallPermission(bp.getName())) {
- // For legacy apps that became modern, install becomes runtime.
- grant = GRANT_UPGRADE;
- } else if (mPromoteSystemApps
- && isSystemApp(ps)
- && mExistingSystemPackages.contains(ps.name)) {
- // For legacy system apps, install becomes runtime.
- // We cannot check hasInstallPermission() for system apps since those
- // permissions were granted implicitly and not persisted pre-M.
- grant = GRANT_UPGRADE;
- } else {
- // For modern apps keep runtime permissions unchanged.
- grant = GRANT_RUNTIME;
- }
- } else if (bp.isSignature()) {
- // For all apps signature permissions are install time ones.
- allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
- if (allowedSig) {
- grant = GRANT_INSTALL;
- }
- }
-
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName);
- }
-
- if (grant != GRANT_DENIED) {
- if (!isSystemApp(ps) && ps.installPermissionsFixed) {
- // If this is an existing, non-system package, then
- // we can't add any new permissions to it.
- if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
- // Except... if this is a permission that was added
- // to the platform (note: need to only do this when
- // updating the platform).
- if (!isNewPlatformPermissionForPackage(perm, pkg)) {
- grant = GRANT_DENIED;
- }
- }
- }
-
- switch (grant) {
- case GRANT_INSTALL: {
- // Revoke this as runtime permission to handle the case of
- // a runtime permission being downgraded to an install one.
- // Also in permission review mode we keep dangerous permissions
- // for legacy apps
- for (int userId : UserManagerService.getInstance().getUserIds()) {
- if (origPermissions.getRuntimePermissionState(
- perm, userId) != null) {
- // Revoke the runtime permission and clear the flags.
- origPermissions.revokeRuntimePermission(bp, userId);
- origPermissions.updatePermissionFlags(bp, userId,
- PackageManager.MASK_PERMISSION_FLAGS, 0);
- // If we revoked a permission permission, we have to write.
- changedRuntimePermissionUserIds = ArrayUtils.appendInt(
- changedRuntimePermissionUserIds, userId);
- }
- }
- // Grant an install permission.
- if (permissionsState.grantInstallPermission(bp) !=
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- changedInstallPermission = true;
- }
- } break;
-
- case GRANT_RUNTIME: {
- // Grant previously granted runtime permissions.
- for (int userId : UserManagerService.getInstance().getUserIds()) {
- PermissionState permissionState = origPermissions
- .getRuntimePermissionState(perm, userId);
- int flags = permissionState != null
- ? permissionState.getFlags() : 0;
- if (origPermissions.hasRuntimePermission(perm, userId)) {
- // Don't propagate the permission in a permission review mode if
- // the former was revoked, i.e. marked to not propagate on upgrade.
- // Note that in a permission review mode install permissions are
- // represented as constantly granted runtime ones since we need to
- // keep a per user state associated with the permission. Also the
- // revoke on upgrade flag is no longer applicable and is reset.
- final boolean revokeOnUpgrade = (flags & PackageManager
- .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
- if (revokeOnUpgrade) {
- flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
- // Since we changed the flags, we have to write.
- changedRuntimePermissionUserIds = ArrayUtils.appendInt(
- changedRuntimePermissionUserIds, userId);
- }
- if (!mPermissionReviewRequired || !revokeOnUpgrade) {
- if (permissionsState.grantRuntimePermission(bp, userId) ==
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- // If we cannot put the permission as it was,
- // we have to write.
- changedRuntimePermissionUserIds = ArrayUtils.appendInt(
- changedRuntimePermissionUserIds, userId);
- }
- }
-
- // If the app supports runtime permissions no need for a review.
- if (mPermissionReviewRequired
- && appSupportsRuntimePermissions
- && (flags & PackageManager
- .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
- // Since we changed the flags, we have to write.
- changedRuntimePermissionUserIds = ArrayUtils.appendInt(
- changedRuntimePermissionUserIds, userId);
- }
- } else if (mPermissionReviewRequired
- && !appSupportsRuntimePermissions) {
- // For legacy apps that need a permission review, every new
- // runtime permission is granted but it is pending a review.
- // We also need to review only platform defined runtime
- // permissions as these are the only ones the platform knows
- // how to disable the API to simulate revocation as legacy
- // apps don't expect to run with revoked permissions.
- if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) {
- if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
- flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
- // We changed the flags, hence have to write.
- changedRuntimePermissionUserIds = ArrayUtils.appendInt(
- changedRuntimePermissionUserIds, userId);
- }
- }
- if (permissionsState.grantRuntimePermission(bp, userId)
- != PermissionsState.PERMISSION_OPERATION_FAILURE) {
- // We changed the permission, hence have to write.
- changedRuntimePermissionUserIds = ArrayUtils.appendInt(
- changedRuntimePermissionUserIds, userId);
- }
- }
- // Propagate the permission flags.
- permissionsState.updatePermissionFlags(bp, userId, flags, flags);
- }
- } break;
-
- case GRANT_UPGRADE: {
- // Grant runtime permissions for a previously held install permission.
- PermissionState permissionState = origPermissions
- .getInstallPermissionState(perm);
- final int flags = permissionState != null ? permissionState.getFlags() : 0;
-
- if (origPermissions.revokeInstallPermission(bp)
- != PermissionsState.PERMISSION_OPERATION_FAILURE) {
- // We will be transferring the permission flags, so clear them.
- origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL,
- PackageManager.MASK_PERMISSION_FLAGS, 0);
- changedInstallPermission = true;
- }
-
- // If the permission is not to be promoted to runtime we ignore it and
- // also its other flags as they are not applicable to install permissions.
- if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) {
- for (int userId : currentUserIds) {
- if (permissionsState.grantRuntimePermission(bp, userId) !=
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- // Transfer the permission flags.
- permissionsState.updatePermissionFlags(bp, userId,
- flags, flags);
- // If we granted the permission, we have to write.
- changedRuntimePermissionUserIds = ArrayUtils.appendInt(
- changedRuntimePermissionUserIds, userId);
- }
- }
- }
- } break;
-
- default: {
- if (packageOfInterest == null
- || packageOfInterest.equals(pkg.packageName)) {
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, "Not granting permission " + perm
- + " to package " + pkg.packageName
- + " because it was previously installed without");
- }
- }
- } break;
- }
- } else {
- if (permissionsState.revokeInstallPermission(bp) !=
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- // Also drop the permission flags.
- permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
- PackageManager.MASK_PERMISSION_FLAGS, 0);
- changedInstallPermission = true;
- Slog.i(TAG, "Un-granting permission " + perm
- + " from package " + pkg.packageName
- + " (protectionLevel=" + bp.getProtectionLevel()
- + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
- + ")");
- } else if (bp.isAppOp()) {
- // Don't print warning for app op permissions, since it is fine for them
- // not to be granted, there is a UI for the user to decide.
- if (DEBUG_PERMISSIONS
- && (packageOfInterest == null
- || packageOfInterest.equals(pkg.packageName))) {
- Slog.i(TAG, "Not granting permission " + perm
- + " to package " + pkg.packageName
- + " (protectionLevel=" + bp.getProtectionLevel()
- + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
- + ")");
- }
- }
- }
- }
-
- if ((changedInstallPermission || replace) && !ps.installPermissionsFixed &&
- !isSystemApp(ps) || isUpdatedSystemApp(ps)){
- // This is the first that we have heard about this package, so the
- // permissions we have now selected are fixed until explicitly
- // changed.
- ps.installPermissionsFixed = true;
- }
-
- // Persist the runtime permissions state for users with changes. If permissions
- // were revoked because no app in the shared user declares them we have to
- // write synchronously to avoid losing runtime permissions state.
- for (int userId : changedRuntimePermissionUserIds) {
- mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked);
- }
- }
-
- private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
- boolean allowed = false;
- final int NP = PackageParser.NEW_PERMISSIONS.length;
- for (int ip=0; ip<NP; ip++) {
- final PackageParser.NewPermissionInfo npi
- = PackageParser.NEW_PERMISSIONS[ip];
- if (npi.name.equals(perm)
- && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
- allowed = true;
- Log.i(TAG, "Auto-granting " + perm + " to old pkg "
- + pkg.packageName);
- break;
- }
- }
- return allowed;
- }
-
- /**
- * Determines whether a package is whitelisted for a particular privapp permission.
- *
- * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted.
- *
- * <p>This handles parent/child apps.
- */
- private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) {
- ArraySet<String> wlPermissions = SystemConfig.getInstance()
- .getPrivAppPermissions(pkg.packageName);
- // Let's check if this package is whitelisted...
- boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm);
- // If it's not, we'll also tail-recurse to the parent.
- return whitelisted ||
- pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage);
- }
-
- private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
- BasePermission bp, PermissionsState origPermissions) {
- boolean oemPermission = bp.isOEM();
- boolean privilegedPermission = bp.isPrivileged();
- boolean privappPermissionsDisable =
- RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
- boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
- boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
- if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivilegedApp()
- && !platformPackage && platformPermission) {
- if (!hasPrivappWhitelistEntry(perm, pkg)) {
- Slog.w(TAG, "Privileged permission " + perm + " for package "
- + pkg.packageName + " - not in privapp-permissions whitelist");
- // Only report violations for apps on system image
- if (!mSystemReady && !pkg.isUpdatedSystemApp()) {
- // it's only a reportable violation if the permission isn't explicitly denied
- final ArraySet<String> deniedPermissions = SystemConfig.getInstance()
- .getPrivAppDenyPermissions(pkg.packageName);
- final boolean permissionViolation =
- deniedPermissions == null || !deniedPermissions.contains(perm);
- if (permissionViolation
- && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
- if (mPrivappPermissionsViolations == null) {
- mPrivappPermissionsViolations = new ArraySet<>();
- }
- mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm);
- } else {
- return false;
- }
- }
- if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
- return false;
- }
- }
- }
- boolean allowed = (compareSignatures(
- bp.getSourcePackageSetting().signatures.mSignatures, pkg.mSignatures)
- == PackageManager.SIGNATURE_MATCH)
- || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
- == PackageManager.SIGNATURE_MATCH);
- if (!allowed && (privilegedPermission || oemPermission)) {
- if (isSystemApp(pkg)) {
- // For updated system applications, a privileged/oem permission
- // is granted only if it had been defined by the original application.
- if (pkg.isUpdatedSystemApp()) {
- final PackageSetting sysPs = mSettings
- .getDisabledSystemPkgLPr(pkg.packageName);
- if (sysPs != null && sysPs.getPermissionsState().hasInstallPermission(perm)) {
- // If the original was granted this permission, we take
- // that grant decision as read and propagate it to the
- // update.
- if ((privilegedPermission && sysPs.isPrivileged())
- || (oemPermission && sysPs.isOem()
- && canGrantOemPermission(sysPs, perm))) {
- allowed = true;
- }
- } else {
- // The system apk may have been updated with an older
- // version of the one on the data partition, but which
- // granted a new system permission that it didn't have
- // before. In this case we do want to allow the app to
- // now get the new permission if the ancestral apk is
- // privileged to get it.
- if (sysPs != null && sysPs.pkg != null
- && isPackageRequestingPermission(sysPs.pkg, perm)
- && ((privilegedPermission && sysPs.isPrivileged())
- || (oemPermission && sysPs.isOem()
- && canGrantOemPermission(sysPs, perm)))) {
- allowed = true;
- }
- // Also if a privileged parent package on the system image or any of
- // its children requested a privileged/oem permission, the updated child
- // packages can also get the permission.
- if (pkg.parentPackage != null) {
- final PackageSetting disabledSysParentPs = mSettings
- .getDisabledSystemPkgLPr(pkg.parentPackage.packageName);
- final PackageParser.Package disabledSysParentPkg =
- (disabledSysParentPs == null || disabledSysParentPs.pkg == null)
- ? null : disabledSysParentPs.pkg;
- if (disabledSysParentPkg != null
- && ((privilegedPermission && disabledSysParentPs.isPrivileged())
- || (oemPermission && disabledSysParentPs.isOem()))) {
- if (isPackageRequestingPermission(disabledSysParentPkg, perm)
- && canGrantOemPermission(disabledSysParentPs, perm)) {
- allowed = true;
- } else if (disabledSysParentPkg.childPackages != null) {
- final int count = disabledSysParentPkg.childPackages.size();
- for (int i = 0; i < count; i++) {
- final PackageParser.Package disabledSysChildPkg =
- disabledSysParentPkg.childPackages.get(i);
- final PackageSetting disabledSysChildPs =
- mSettings.getDisabledSystemPkgLPr(
- disabledSysChildPkg.packageName);
- if (isPackageRequestingPermission(disabledSysChildPkg, perm)
- && canGrantOemPermission(
- disabledSysChildPs, perm)) {
- allowed = true;
- break;
- }
- }
- }
- }
- }
- }
- } else {
- allowed = (privilegedPermission && isPrivilegedApp(pkg))
- || (oemPermission && isOemApp(pkg)
- && canGrantOemPermission(
- mSettings.getPackageLPr(pkg.packageName), perm));
- }
- }
- }
- if (!allowed) {
- if (!allowed
- && bp.isPre23()
- && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
- // If this was a previously normal/dangerous permission that got moved
- // to a system permission as part of the runtime permission redesign, then
- // we still want to blindly grant it to old apps.
- allowed = true;
- }
- if (!allowed && bp.isInstaller()
- && pkg.packageName.equals(mRequiredInstallerPackage)) {
- // If this permission is to be granted to the system installer and
- // this app is an installer, then it gets the permission.
- allowed = true;
- }
- if (!allowed && bp.isVerifier()
- && pkg.packageName.equals(mRequiredVerifierPackage)) {
- // If this permission is to be granted to the system verifier and
- // this app is a verifier, then it gets the permission.
- allowed = true;
- }
- if (!allowed && bp.isPreInstalled()
- && isSystemApp(pkg)) {
- // Any pre-installed system app is allowed to get this permission.
- allowed = true;
- }
- if (!allowed && bp.isDevelopment()) {
- // For development permissions, a development permission
- // is granted only if it was already granted.
- allowed = origPermissions.hasInstallPermission(perm);
- }
- if (!allowed && bp.isSetup()
- && pkg.packageName.equals(mSetupWizardPackage)) {
- // If this permission is to be granted to the system setup wizard and
- // this app is a setup wizard, then it gets the permission.
- allowed = true;
- }
- }
- return allowed;
- }
-
- private static boolean canGrantOemPermission(PackageSetting ps, String permission) {
- if (!ps.isOem()) {
- return false;
- }
- // all oem permissions must explicitly be granted or denied
- final Boolean granted =
- SystemConfig.getInstance().getOemPermissions(ps.name).get(permission);
- if (granted == null) {
- throw new IllegalStateException("OEM permission" + permission + " requested by package "
- + ps.name + " must be explicitly declared granted or not");
- }
- return Boolean.TRUE == granted;
- }
-
- private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) {
- final int permCount = pkg.requestedPermissions.size();
- for (int j = 0; j < permCount; j++) {
- String requestedPermission = pkg.requestedPermissions.get(j);
- if (permission.equals(requestedPermission)) {
- return true;
- }
- }
- return false;
- }
final class ActivityIntentResolver
extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
@@ -16491,7 +15749,7 @@
}
// don't allow a system upgrade unless the upgrade hash matches
- if (oldPackage.restrictUpdateHash != null && oldPackage.isSystemApp()) {
+ if (oldPackage.restrictUpdateHash != null && oldPackage.isSystem()) {
byte[] digestBytes = null;
try {
final MessageDigest digest = MessageDigest.getInstance("SHA-512");
@@ -16758,7 +16016,9 @@
setInstallerPackageNameLPw(deletedPackage, installerPackageName);
// Update permissions for restored package
- updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL);
+ mPermissionManager.updatePermissions(
+ deletedPackage.packageName, deletedPackage, false, mPackages.values(),
+ mPermissionCallback);
mSettings.writeLPr();
}
@@ -16900,7 +16160,9 @@
setInstallerPackageNameLPw(deletedPackage, installerPackageName);
// Update permissions for restored package
- updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL);
+ mPermissionManager.updatePermissions(
+ deletedPackage.packageName, deletedPackage, false, mPackages.values(),
+ mPermissionCallback);
mSettings.writeLPr();
}
@@ -17013,12 +16275,12 @@
}
}
- private void updateSettingsInternalLI(PackageParser.Package newPackage,
+ private void updateSettingsInternalLI(PackageParser.Package pkg,
String installerPackageName, int[] allUsers, int[] installedForUsers,
PackageInstalledInfo res, UserHandle user, int installReason) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
- String pkgName = newPackage.packageName;
+ String pkgName = pkg.packageName;
synchronized (mPackages) {
//write settings. the installStatus will be incomplete at this stage.
//note that the new package setting would have already been
@@ -17030,18 +16292,18 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.codePath);
+ if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.codePath);
synchronized (mPackages) {
- updatePermissionsLPw(newPackage.packageName, newPackage,
- UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
- ? UPDATE_PERMISSIONS_ALL : 0));
+// NOTE: This changes slightly to include UPDATE_PERMISSIONS_ALL regardless of the size of pkg.permissions
+ mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(),
+ mPermissionCallback);
// For system-bundled packages, we assume that installing an upgraded version
// of the package implies that the user actually wants to run that new code,
// so we enable the package.
PackageSetting ps = mSettings.mPackages.get(pkgName);
final int userId = user.getIdentifier();
if (ps != null) {
- if (isSystemApp(newPackage)) {
+ if (isSystemApp(pkg)) {
if (DEBUG_INSTALL) {
Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
}
@@ -17101,8 +16363,8 @@
mSettings.writeKernelMappingLPr(ps);
}
res.name = pkgName;
- res.uid = newPackage.applicationInfo.uid;
- res.pkg = newPackage;
+ res.uid = pkg.applicationInfo.uid;
+ res.pkg = pkg;
mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE);
mSettings.setInstallerPackageName(pkgName, installerPackageName);
res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
@@ -17801,18 +17063,6 @@
return installFlags;
}
- private String getVolumeUuidForPackage(PackageParser.Package pkg) {
- if (isExternal(pkg)) {
- if (TextUtils.isEmpty(pkg.volumeUuid)) {
- return StorageManager.UUID_PRIMARY_PHYSICAL;
- } else {
- return pkg.volumeUuid;
- }
- } else {
- return StorageManager.UUID_PRIVATE_INTERNAL;
- }
- }
-
private VersionInfo getSettingsVersionForPackage(PackageParser.Package pkg) {
if (isExternal(pkg)) {
if (TextUtils.isEmpty(pkg.volumeUuid)) {
@@ -18457,7 +17707,8 @@
if (outInfo != null) {
outInfo.removedAppId = removedAppId;
}
- updatePermissionsLPw(deletedPs.name, null, 0);
+ mPermissionManager.updatePermissions(
+ deletedPs.name, null, false, mPackages.values(), mPermissionCallback);
if (deletedPs.sharedUser != null) {
// Remove permissions associated with package. Since runtime
// permissions are per user we have to kill the removed package
@@ -18660,21 +17911,21 @@
parseFlags |= PackageParser.PARSE_IS_OEM;
}
- final PackageParser.Package newPkg =
+ final PackageParser.Package pkg =
scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null);
try {
// update shared libraries for the newly re-installed system package
- updateSharedLibrariesLPr(newPkg, null);
+ updateSharedLibrariesLPr(pkg, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
}
- prepareAppDataAfterInstallLIF(newPkg);
+ prepareAppDataAfterInstallLIF(pkg);
// writer
synchronized (mPackages) {
- PackageSetting ps = mSettings.mPackages.get(newPkg.packageName);
+ PackageSetting ps = mSettings.mPackages.get(pkg.packageName);
// Propagate the permissions state as we do not want to drop on the floor
// runtime permissions. The update permissions method below will take
@@ -18682,8 +17933,8 @@
if (origPermissionState != null) {
ps.getPermissionsState().copyFrom(origPermissionState);
}
- updatePermissionsLPw(newPkg.packageName, newPkg,
- UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
+ mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(),
+ mPermissionCallback);
final boolean applyUserRestrictions
= (allUserHandles != null) && (origUserHandles != null);
@@ -18716,7 +17967,7 @@
mSettings.writeLPr();
}
}
- return newPkg;
+ return pkg;
}
private boolean deleteInstalledPackageLIF(PackageSetting ps,
@@ -19333,7 +18584,7 @@
// If permission review is enabled and this is a legacy app, mark the
// permission as requiring a review as this is the initial state.
int flags = 0;
- if (mPermissionReviewRequired
+ if (mSettings.mPermissions.mPermissionReviewRequired
&& ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
}
@@ -20692,7 +19943,7 @@
// data partition and then replace the version on the system partition.
final PackageParser.Package deletedPkg = pkgSetting.pkg;
final boolean isSystemStub = deletedPkg.isStub
- && deletedPkg.isSystemApp();
+ && deletedPkg.isSystem();
if (isSystemStub
&& (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
@@ -20732,22 +19983,23 @@
synchronized (mPackages) {
disableSystemPackageLPw(deletedPkg, tmpPkg);
}
- final PackageParser.Package newPkg;
+ final PackageParser.Package pkg;
try (PackageFreezer freezer =
freezePackage(deletedPkg.packageName, "setEnabledSetting")) {
final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| PackageParser.PARSE_ENFORCE_CODE;
- newPkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/,
+ pkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/,
0 /*currentTime*/, null /*user*/);
- prepareAppDataAfterInstallLIF(newPkg);
+ prepareAppDataAfterInstallLIF(pkg);
synchronized (mPackages) {
try {
- updateSharedLibrariesLPr(newPkg, null);
+ updateSharedLibrariesLPr(pkg, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e);
}
- updatePermissionsLPw(newPkg.packageName, newPkg,
- UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
+ mPermissionManager.updatePermissions(
+ pkg.packageName, pkg, true, mPackages.values(),
+ mPermissionCallback);
mSettings.writeLPr();
}
} catch (PackageManagerException e) {
@@ -20787,11 +20039,11 @@
}
return;
}
- clearAppDataLIF(newPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
+ clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
| FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
- clearAppProfilesLIF(newPkg, UserHandle.USER_ALL);
- mDexManager.notifyPackageUpdated(newPkg.packageName,
- newPkg.baseCodePath, newPkg.splitCodePaths);
+ clearAppProfilesLIF(pkg, UserHandle.USER_ALL);
+ mDexManager.notifyPackageUpdated(pkg.packageName,
+ pkg.baseCodePath, pkg.splitCodePaths);
}
}
if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
@@ -21102,8 +20354,9 @@
// permissions, ensure permissions are updated. Beware of dragons if you
// try optimizing this.
synchronized (mPackages) {
- updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL,
- UPDATE_PERMISSIONS_ALL);
+ mPermissionManager.updateAllPermissions(
+ StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(),
+ mPermissionCallback);
}
// Kick off any messages waiting for system ready
@@ -21152,10 +20405,7 @@
sUserManager.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);
reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL);
- if (mPrivappPermissionsViolations != null) {
- throw new IllegalStateException("Signature|privileged permissions not in "
- + "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
- }
+ mPermissionManager.systemReady();
}
public void waitForAppDataPrepared() {
@@ -22140,13 +21390,13 @@
}
synchronized (mPackages) {
- int updateFlags = UPDATE_PERMISSIONS_ALL;
- if (ver.sdkVersion != mSdkVersion) {
+ final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion);
+ if (sdkUpdated) {
logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
+ mSdkVersion + "; regranting permissions for " + volumeUuid);
- updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
}
- updatePermissionsLocked(null, null, volumeUuid, updateFlags);
+ mPermissionManager.updateAllPermissions(volumeUuid, sdkUpdated, mPackages.values(),
+ mPermissionCallback);
// Yay, everything is now upgraded
ver.forceCurrent();
@@ -22594,7 +21844,7 @@
* requested by the app.
*/
private boolean maybeMigrateAppDataLIF(PackageParser.Package pkg, int userId) {
- if (pkg.isSystemApp() && !StorageManager.isFileEncryptedNativeOrEmulated()
+ if (pkg.isSystem() && !StorageManager.isFileEncryptedNativeOrEmulated()
&& PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
final int storageTarget = pkg.applicationInfo.isDefaultToDeviceProtectedStorage()
? StorageManager.FLAG_STORAGE_DE : StorageManager.FLAG_STORAGE_CE;
@@ -23159,9 +22409,11 @@
// permissions to keep per user flag state whether review is needed.
// Hence, if a new user is added we have to propagate dangerous
// permission grants for these legacy apps.
- if (mPermissionReviewRequired) {
- updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
- | UPDATE_PERMISSIONS_REPLACE_ALL);
+ if (mSettings.mPermissions.mPermissionReviewRequired) {
+// NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
+ mPermissionManager.updateAllPermissions(
+ StorageManager.UUID_PRIVATE_INTERNAL, true, mPackages.values(),
+ mPermissionCallback);
}
}
}
@@ -23606,13 +22858,6 @@
}
@Override
- public PackageParser.PermissionGroup getPermissionGroupTEMP(String groupName) {
- synchronized (mPackages) {
- return mPermissionGroups.get(groupName);
- }
- }
-
- @Override
public boolean isInstantApp(String packageName, int userId) {
return PackageManagerService.this.isInstantApp(packageName, userId);
}
@@ -23750,24 +22995,8 @@
@Override
public boolean isPermissionsReviewRequired(String packageName, int userId) {
synchronized (mPackages) {
- // If we do not support permission review, done.
- if (!mPermissionReviewRequired) {
- return false;
- }
-
- PackageSetting packageSetting = mSettings.mPackages.get(packageName);
- if (packageSetting == null) {
- return false;
- }
-
- // Permission review applies only to apps not supporting the new permission model.
- if (packageSetting.pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
- return false;
- }
-
- // Legacy apps have the permission and get user consent on launch.
- PermissionsState permissionsState = packageSetting.getPermissionsState();
- return permissionsState.isPermissionReviewRequired(userId);
+ return mPermissionManager.isPermissionsReviewRequired(
+ mPackages.get(packageName), userId);
}
}
@@ -23921,6 +23150,16 @@
}
@Override
+ public boolean isLegacySystemApp(Package pkg) {
+ synchronized (mPackages) {
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ return mPromoteSystemApps
+ && ps.isSystem()
+ && mExistingSystemPackages.contains(ps.name);
+ }
+ }
+
+ @Override
public List<PackageInfo> getOverlayPackages(int userId) {
final ArrayList<PackageInfo> overlayPackages = new ArrayList<PackageInfo>();
synchronized (mPackages) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 19b0d9b..cf9e6f3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -52,7 +52,16 @@
// Load the property for the given reason and check for validity. This will throw an
// exception in case the reason or value are invalid.
private static String getAndCheckValidity(int reason) {
- String sysPropValue = SystemProperties.get(getSystemPropertyName(reason));
+ String sysPropName = getSystemPropertyName(reason);
+ String sysPropValue;
+ // TODO: This is a temporary hack to keep marlin booting on aosp/master while we
+ // figure out how to deal with these system properties that currently appear on
+ // vendor.
+ if ("pm.dexopt.inactive".equals(sysPropName)) {
+ sysPropValue = "verify";
+ } else {
+ sysPropValue = SystemProperties.get(sysPropName);
+ }
if (sysPropValue == null || sysPropValue.isEmpty() ||
!DexFile.isValidCompilerFilter(sysPropValue)) {
throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid "
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1fea003..807eb1a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -16,14 +16,17 @@
package com.android.server.pm;
+import android.accounts.IAccountManager;
import android.app.ActivityManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
@@ -41,6 +44,7 @@
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
import android.content.pm.VersionedPackage;
import android.content.res.AssetManager;
import android.content.res.Resources;
@@ -49,15 +53,24 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.IUserManager;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.ShellCommand;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
import android.text.TextUtils;
-import android.util.ArrayMap;
+import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.PrintWriterPrinter;
+
import com.android.internal.content.PackageHelper;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.SizedInputStream;
import com.android.server.SystemConfig;
@@ -66,7 +79,6 @@
import libcore.io.IoUtils;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -81,11 +93,15 @@
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+
class PackageManagerShellCommand extends ShellCommand {
/** Path for streaming APK content */
private static final String STDIN_PATH = "-";
- /** Whether or not APK content must be streamed from stdin */
- private static final boolean FORCE_STREAM_INSTALL = true;
final IPackageManager mInterface;
final private WeakHashMap<String, Resources> mResourceCache =
@@ -107,6 +123,20 @@
final PrintWriter pw = getOutPrintWriter();
try {
switch(cmd) {
+ case "path":
+ return runPath();
+ case "dump":
+ return runDump();
+ case "list":
+ return runList();
+ case "resolve-activity":
+ return runResolveActivity();
+ case "query-activities":
+ return runQueryIntentActivities();
+ case "query-services":
+ return runQueryIntentServices();
+ case "query-receivers":
+ return runQueryIntentReceivers();
case "install":
return runInstall();
case "install-abandon":
@@ -122,44 +152,99 @@
return runInstallWrite();
case "install-existing":
return runInstallExisting();
+ case "set-install-location":
+ return runSetInstallLocation();
+ case "get-install-location":
+ return runGetInstallLocation();
+ case "move-package":
+ return runMovePackage();
+ case "move-primary-storage":
+ return runMovePrimaryStorage();
case "compile":
return runCompile();
case "reconcile-secondary-dex-files":
return runreconcileSecondaryDexFiles();
+ case "force-dex-opt":
+ return runForceDexOpt();
case "bg-dexopt-job":
return runDexoptJob();
case "dump-profiles":
return runDumpProfiles();
- case "list":
- return runList();
case "uninstall":
return runUninstall();
- case "resolve-activity":
- return runResolveActivity();
- case "query-activities":
- return runQueryIntentActivities();
- case "query-services":
- return runQueryIntentServices();
- case "query-receivers":
- return runQueryIntentReceivers();
+ case "clear":
+ return runClear();
+ case "enable":
+ return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ case "disable":
+ return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ case "disable-user":
+ return runSetEnabledSetting(
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
+ case "disable-until-used":
+ return runSetEnabledSetting(
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+ case "default-state":
+ return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+ case "hide":
+ return runSetHiddenSetting(true);
+ case "unhide":
+ return runSetHiddenSetting(false);
case "suspend":
return runSuspend(true);
case "unsuspend":
return runSuspend(false);
- case "set-home-activity":
- return runSetHomeActivity();
+ case "grant":
+ return runGrantRevokePermission(true);
+ case "revoke":
+ return runGrantRevokePermission(false);
+ case "reset-permissions":
+ return runResetPermissions();
+ case "set-permission-enforced":
+ return runSetPermissionEnforced();
case "get-privapp-permissions":
return runGetPrivappPermissions();
case "get-privapp-deny-permissions":
return runGetPrivappDenyPermissions();
case "get-oem-permissions":
return runGetOemPermissions();
+ case "set-app-link":
+ return runSetAppLink();
+ case "get-app-link":
+ return runGetAppLink();
+ case "trim-caches":
+ return runTrimCaches();
+ case "create-user":
+ return runCreateUser();
+ case "remove-user":
+ return runRemoveUser();
+ case "set-user-restriction":
+ return runSetUserRestriction();
+ case "get-max-users":
+ return runGetMaxUsers();
+ case "set-home-activity":
+ return runSetHomeActivity();
+ case "set-installer":
+ return runSetInstaller();
case "get-instantapp-resolver":
return runGetInstantAppResolver();
case "has-feature":
return runHasFeature();
- default:
+ default: {
+ String nextArg = getNextArg();
+ if (nextArg == null) {
+ if (cmd.equalsIgnoreCase("-l")) {
+ return runListPackages(false);
+ } else if (cmd.equalsIgnoreCase("-lf")) {
+ return runListPackages(true);
+ }
+ } else if (getNextArg() == null) {
+ if (cmd.equalsIgnoreCase("-p")) {
+ return displayPackageFilePath(nextArg, UserHandle.USER_SYSTEM);
+ }
+ }
return handleDefaultCommands(cmd);
+ }
}
} catch (RemoteException e) {
pw.println("Remote exception: " + e);
@@ -168,373 +253,64 @@
}
private void setParamsSize(InstallParams params, String inPath) {
- // If we're forced to stream the package, the params size
- // must be set via command-line argument. There's nothing
- // to do here.
- if (FORCE_STREAM_INSTALL) {
- return;
- }
- final PrintWriter pw = getOutPrintWriter();
if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
- File file = new File(inPath);
- if (file.isFile()) {
+ final ParcelFileDescriptor fd = openFileForSystem(inPath, "r");
+ if (fd == null) {
+ getErrPrintWriter().println("Error: Can't open file: " + inPath);
+ throw new IllegalArgumentException("Error: Can't open file: " + inPath);
+ }
+ try {
+ ApkLite baseApk = PackageParser.parseApkLite(fd.getFileDescriptor(), inPath, 0);
+ PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
+ null, null);
+ params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
+ pkgLite, params.sessionParams.abiOverride));
+ } catch (PackageParserException | IOException e) {
+ getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
+ throw new IllegalArgumentException(
+ "Error: Failed to parse APK file: " + inPath, e);
+ } finally {
try {
- ApkLite baseApk = PackageParser.parseApkLite(file, 0);
- PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
- null, null);
- params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
- pkgLite, params.sessionParams.abiOverride));
- } catch (PackageParserException | IOException e) {
- pw.println("Error: Failed to parse APK file: " + file);
- throw new IllegalArgumentException(
- "Error: Failed to parse APK file: " + file, e);
+ fd.close();
+ } catch (IOException e) {
}
- } else {
- pw.println("Error: Can't open non-file: " + inPath);
- throw new IllegalArgumentException("Error: Can't open non-file: " + inPath);
}
}
}
-
- private int runInstall() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- final InstallParams params = makeInstallParams();
- final String inPath = getNextArg();
-
- setParamsSize(params, inPath);
- final int sessionId = doCreateSession(params.sessionParams,
- params.installerPackageName, params.userId);
- boolean abandonSession = true;
- try {
- if (inPath == null && params.sessionParams.sizeBytes == -1) {
- pw.println("Error: must either specify a package size or an APK file");
- return 1;
+ /**
+ * Displays the package file for a package.
+ * @param pckg
+ */
+ private int displayPackageFilePath(String pckg, int userId) throws RemoteException {
+ PackageInfo info = mInterface.getPackageInfo(pckg, 0, userId);
+ if (info != null && info.applicationInfo != null) {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.print("package:");
+ pw.println(info.applicationInfo.sourceDir);
+ if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
+ for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
+ pw.print("package:");
+ pw.println(splitSourceDir);
+ }
}
- if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
- false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- if (doCommitSession(sessionId, false /*logSuccess*/)
- != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- abandonSession = false;
- pw.println("Success");
return 0;
- } finally {
- if (abandonSession) {
- try {
- doAbandonSession(sessionId, false /*logSuccess*/);
- } catch (Exception ignore) {
- }
- }
}
+ return 1;
}
- private int runSuspend(boolean suspendedState) {
- final PrintWriter pw = getOutPrintWriter();
+ private int runPath() throws RemoteException {
int userId = UserHandle.USER_SYSTEM;
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "--user":
- userId = UserHandle.parseUserArg(getNextArgRequired());
- break;
- default:
- pw.println("Error: Unknown option: " + opt);
- return 1;
- }
+ String option = getNextOption();
+ if (option != null && option.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
}
- String packageName = getNextArg();
- if (packageName == null) {
- pw.println("Error: package name not specified");
+ String pkg = getNextArgRequired();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified");
return 1;
}
-
- try {
- mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
- userId);
- pw.println("Package " + packageName + " new suspended state: "
- + mInterface.isPackageSuspendedForUser(packageName, userId));
- return 0;
- } catch (RemoteException | IllegalArgumentException e) {
- pw.println(e.toString());
- return 1;
- }
- }
-
- private int runInstallAbandon() throws RemoteException {
- final int sessionId = Integer.parseInt(getNextArg());
- return doAbandonSession(sessionId, true /*logSuccess*/);
- }
-
- private int runInstallCommit() throws RemoteException {
- final int sessionId = Integer.parseInt(getNextArg());
- return doCommitSession(sessionId, true /*logSuccess*/);
- }
-
- private int runInstallCreate() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- final InstallParams installParams = makeInstallParams();
- final int sessionId = doCreateSession(installParams.sessionParams,
- installParams.installerPackageName, installParams.userId);
-
- // NOTE: adb depends on parsing this string
- pw.println("Success: created install session [" + sessionId + "]");
- return 0;
- }
-
- private int runInstallWrite() throws RemoteException {
- long sizeBytes = -1;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- if (opt.equals("-S")) {
- sizeBytes = Long.parseLong(getNextArg());
- } else {
- throw new IllegalArgumentException("Unknown option: " + opt);
- }
- }
-
- final int sessionId = Integer.parseInt(getNextArg());
- final String splitName = getNextArg();
- final String path = getNextArg();
- return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
- }
-
- private int runInstallRemove() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
-
- final int sessionId = Integer.parseInt(getNextArg());
-
- final String splitName = getNextArg();
- if (splitName == null) {
- pw.println("Error: split name not specified");
- return 1;
- }
- return doRemoveSplit(sessionId, splitName, true /*logSuccess*/);
- }
-
- private int runInstallExisting() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- int userId = UserHandle.USER_SYSTEM;
- int installFlags = 0;
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "--user":
- userId = UserHandle.parseUserArg(getNextArgRequired());
- break;
- case "--ephemeral":
- case "--instant":
- installFlags |= PackageManager.INSTALL_INSTANT_APP;
- installFlags &= ~PackageManager.INSTALL_FULL_APP;
- break;
- case "--full":
- installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
- installFlags |= PackageManager.INSTALL_FULL_APP;
- break;
- default:
- pw.println("Error: Unknown option: " + opt);
- return 1;
- }
- }
-
- final String packageName = getNextArg();
- if (packageName == null) {
- pw.println("Error: package name not specified");
- return 1;
- }
-
- try {
- final int res = mInterface.installExistingPackageAsUser(packageName, userId,
- installFlags, PackageManager.INSTALL_REASON_UNKNOWN);
- if (res == PackageManager.INSTALL_FAILED_INVALID_URI) {
- throw new NameNotFoundException("Package " + packageName + " doesn't exist");
- }
- pw.println("Package " + packageName + " installed for user: " + userId);
- return 0;
- } catch (RemoteException | NameNotFoundException e) {
- pw.println(e.toString());
- return 1;
- }
- }
-
- private int runCompile() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
- boolean forceCompilation = false;
- boolean allPackages = false;
- boolean clearProfileData = false;
- String compilerFilter = null;
- String compilationReason = null;
- String checkProfilesRaw = null;
- boolean secondaryDex = false;
- String split = null;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "-a":
- allPackages = true;
- break;
- case "-c":
- clearProfileData = true;
- break;
- case "-f":
- forceCompilation = true;
- break;
- case "-m":
- compilerFilter = getNextArgRequired();
- break;
- case "-r":
- compilationReason = getNextArgRequired();
- break;
- case "--check-prof":
- checkProfilesRaw = getNextArgRequired();
- break;
- case "--reset":
- forceCompilation = true;
- clearProfileData = true;
- compilationReason = "install";
- break;
- case "--secondary-dex":
- secondaryDex = true;
- break;
- case "--split":
- split = getNextArgRequired();
- break;
- default:
- pw.println("Error: Unknown option: " + opt);
- return 1;
- }
- }
-
- if (checkProfilesRaw != null) {
- if ("true".equals(checkProfilesRaw)) {
- checkProfiles = true;
- } else if ("false".equals(checkProfilesRaw)) {
- checkProfiles = false;
- } else {
- pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\".");
- return 1;
- }
- }
-
- if (compilerFilter != null && compilationReason != null) {
- pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " +
- "at the same time");
- return 1;
- }
- if (compilerFilter == null && compilationReason == null) {
- pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " +
- "reason (\"-r\") at the same time");
- return 1;
- }
-
- if (allPackages && split != null) {
- pw.println("-a cannot be specified together with --split");
- return 1;
- }
-
- if (secondaryDex && split != null) {
- pw.println("--secondary-dex cannot be specified together with --split");
- return 1;
- }
-
- String targetCompilerFilter;
- if (compilerFilter != null) {
- if (!DexFile.isValidCompilerFilter(compilerFilter)) {
- pw.println("Error: \"" + compilerFilter +
- "\" is not a valid compilation filter.");
- return 1;
- }
- targetCompilerFilter = compilerFilter;
- } else {
- int reason = -1;
- for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
- if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals(
- compilationReason)) {
- reason = i;
- break;
- }
- }
- if (reason == -1) {
- pw.println("Error: Unknown compilation reason: " + compilationReason);
- return 1;
- }
- targetCompilerFilter =
- PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason);
- }
-
-
- List<String> packageNames = null;
- if (allPackages) {
- packageNames = mInterface.getAllPackages();
- } else {
- String packageName = getNextArg();
- if (packageName == null) {
- pw.println("Error: package name not specified");
- return 1;
- }
- packageNames = Collections.singletonList(packageName);
- }
-
- List<String> failedPackages = new ArrayList<>();
- for (String packageName : packageNames) {
- if (clearProfileData) {
- mInterface.clearApplicationProfileData(packageName);
- }
-
- boolean result = secondaryDex
- ? mInterface.performDexOptSecondary(packageName,
- targetCompilerFilter, forceCompilation)
- : mInterface.performDexOptMode(packageName,
- checkProfiles, targetCompilerFilter, forceCompilation,
- true /* bootComplete */, split);
- if (!result) {
- failedPackages.add(packageName);
- }
- }
-
- if (failedPackages.isEmpty()) {
- pw.println("Success");
- return 0;
- } else if (failedPackages.size() == 1) {
- pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled");
- return 1;
- } else {
- pw.print("Failure: the following packages could not be compiled: ");
- boolean is_first = true;
- for (String packageName : failedPackages) {
- if (is_first) {
- is_first = false;
- } else {
- pw.print(", ");
- }
- pw.print(packageName);
- }
- pw.println();
- return 1;
- }
- }
-
- private int runreconcileSecondaryDexFiles() throws RemoteException {
- String packageName = getNextArg();
- mInterface.reconcileSecondaryDexFiles(packageName);
- return 0;
- }
-
- private int runDexoptJob() throws RemoteException {
- boolean result = mInterface.runBackgroundDexoptJob();
- return result ? 0 : -1;
- }
-
- private int runDumpProfiles() throws RemoteException {
- String packageName = getNextArg();
- mInterface.dumpProfiles(packageName);
- return 0;
+ return displayPackageFilePath(pkg, userId);
}
private int runList() throws RemoteException {
@@ -558,6 +334,11 @@
return runListPermissionGroups();
case "permissions":
return runListPermissions();
+ case "users":
+ ServiceManager.getService("user").shellCommand(
+ getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(),
+ new String[] { "list" }, getShellCallback(), adoptResultReceiver());
+ return 0;
}
pw.println("Error: unknown list type '" + type + "'");
return -1;
@@ -590,7 +371,7 @@
pw.println();
} else {
pw.println("reqGlEsVersion=0x"
- + Integer.toHexString(fi.reqGlEsVersion));
+ + Integer.toHexString(fi.reqGlEsVersion));
}
}
return 0;
@@ -872,111 +653,6 @@
return 0;
}
- private int runUninstall() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- int flags = 0;
- int userId = UserHandle.USER_ALL;
- int versionCode = PackageManager.VERSION_CODE_HIGHEST;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "-k":
- flags |= PackageManager.DELETE_KEEP_DATA;
- break;
- case "--user":
- userId = UserHandle.parseUserArg(getNextArgRequired());
- break;
- case "--versionCode":
- versionCode = Integer.parseInt(getNextArgRequired());
- break;
- default:
- pw.println("Error: Unknown option: " + opt);
- return 1;
- }
- }
-
- final String packageName = getNextArg();
- if (packageName == null) {
- pw.println("Error: package name not specified");
- return 1;
- }
-
- // if a split is specified, just remove it and not the whole package
- final String splitName = getNextArg();
- if (splitName != null) {
- return runRemoveSplit(packageName, splitName);
- }
-
- userId = translateUserId(userId, "runUninstall");
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
- flags |= PackageManager.DELETE_ALL_USERS;
- } else {
- final PackageInfo info = mInterface.getPackageInfo(packageName,
- PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
- if (info == null) {
- pw.println("Failure [not installed for " + userId + "]");
- return 1;
- }
- final boolean isSystem =
- (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- // If we are being asked to delete a system app for just one
- // user set flag so it disables rather than reverting to system
- // version of the app.
- if (isSystem) {
- flags |= PackageManager.DELETE_SYSTEM_APP;
- }
- }
-
- final LocalIntentReceiver receiver = new LocalIntentReceiver();
- mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName,
- versionCode), null /*callerPackageName*/, flags,
- receiver.getIntentSender(), userId);
-
- final Intent result = receiver.getResult();
- final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
- if (status == PackageInstaller.STATUS_SUCCESS) {
- pw.println("Success");
- return 0;
- } else {
- pw.println("Failure ["
- + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
- return 1;
- }
- }
-
- private int runRemoveSplit(String packageName, String splitName) throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
- sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
- sessionParams.appPackageName = packageName;
- final int sessionId =
- doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL);
- boolean abandonSession = true;
- try {
- if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/)
- != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- if (doCommitSession(sessionId, false /*logSuccess*/)
- != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- abandonSession = false;
- pw.println("Success");
- return 0;
- } finally {
- if (abandonSession) {
- try {
- doAbandonSession(sessionId, false /*logSuccess*/);
- } catch (Exception ignore) {
- }
- }
- }
- }
-
private Intent parseIntentAndUser() throws URISyntaxException {
mTargetUser = UserHandle.USER_CURRENT;
mBrief = false;
@@ -1154,6 +830,1029 @@
return 0;
}
+ private int runInstall() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final InstallParams params = makeInstallParams();
+ final String inPath = getNextArg();
+
+ setParamsSize(params, inPath);
+ final int sessionId = doCreateSession(params.sessionParams,
+ params.installerPackageName, params.userId);
+ boolean abandonSession = true;
+ try {
+ if (inPath == null && params.sessionParams.sizeBytes == -1) {
+ pw.println("Error: must either specify a package size or an APK file");
+ return 1;
+ }
+ if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
+ false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
+ if (doCommitSession(sessionId, false /*logSuccess*/)
+ != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
+ abandonSession = false;
+ pw.println("Success");
+ return 0;
+ } finally {
+ if (abandonSession) {
+ try {
+ doAbandonSession(sessionId, false /*logSuccess*/);
+ } catch (Exception ignore) {
+ }
+ }
+ }
+ }
+
+ private int runInstallAbandon() throws RemoteException {
+ final int sessionId = Integer.parseInt(getNextArg());
+ return doAbandonSession(sessionId, true /*logSuccess*/);
+ }
+
+ private int runInstallCommit() throws RemoteException {
+ final int sessionId = Integer.parseInt(getNextArg());
+ return doCommitSession(sessionId, true /*logSuccess*/);
+ }
+
+ private int runInstallCreate() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final InstallParams installParams = makeInstallParams();
+ final int sessionId = doCreateSession(installParams.sessionParams,
+ installParams.installerPackageName, installParams.userId);
+
+ // NOTE: adb depends on parsing this string
+ pw.println("Success: created install session [" + sessionId + "]");
+ return 0;
+ }
+
+ private int runInstallWrite() throws RemoteException {
+ long sizeBytes = -1;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("-S")) {
+ sizeBytes = Long.parseLong(getNextArg());
+ } else {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ }
+
+ final int sessionId = Integer.parseInt(getNextArg());
+ final String splitName = getNextArg();
+ final String path = getNextArg();
+ return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
+ }
+
+ private int runInstallRemove() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+
+ final int sessionId = Integer.parseInt(getNextArg());
+
+ final String splitName = getNextArg();
+ if (splitName == null) {
+ pw.println("Error: split name not specified");
+ return 1;
+ }
+ return doRemoveSplit(sessionId, splitName, true /*logSuccess*/);
+ }
+
+ private int runInstallExisting() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int userId = UserHandle.USER_SYSTEM;
+ int installFlags = 0;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ case "--ephemeral":
+ case "--instant":
+ installFlags |= PackageManager.INSTALL_INSTANT_APP;
+ installFlags &= ~PackageManager.INSTALL_FULL_APP;
+ break;
+ case "--full":
+ installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
+ installFlags |= PackageManager.INSTALL_FULL_APP;
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ final String packageName = getNextArg();
+ if (packageName == null) {
+ pw.println("Error: package name not specified");
+ return 1;
+ }
+
+ try {
+ final int res = mInterface.installExistingPackageAsUser(packageName, userId,
+ installFlags, PackageManager.INSTALL_REASON_UNKNOWN);
+ if (res == PackageManager.INSTALL_FAILED_INVALID_URI) {
+ throw new NameNotFoundException("Package " + packageName + " doesn't exist");
+ }
+ pw.println("Package " + packageName + " installed for user: " + userId);
+ return 0;
+ } catch (RemoteException | NameNotFoundException e) {
+ pw.println(e.toString());
+ return 1;
+ }
+ }
+
+ private int runSetInstallLocation() throws RemoteException {
+ int loc;
+
+ String arg = getNextArg();
+ if (arg == null) {
+ getErrPrintWriter().println("Error: no install location specified.");
+ return 1;
+ }
+ try {
+ loc = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: install location has to be a number.");
+ return 1;
+ }
+ if (!mInterface.setInstallLocation(loc)) {
+ getErrPrintWriter().println("Error: install location has to be a number.");
+ return 1;
+ }
+ return 0;
+ }
+
+ private int runGetInstallLocation() throws RemoteException {
+ int loc = mInterface.getInstallLocation();
+ String locStr = "invalid";
+ if (loc == PackageHelper.APP_INSTALL_AUTO) {
+ locStr = "auto";
+ } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
+ locStr = "internal";
+ } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+ locStr = "external";
+ }
+ getOutPrintWriter().println(loc + "[" + locStr + "]");
+ return 0;
+ }
+
+ public int runMovePackage() throws RemoteException {
+ final String packageName = getNextArg();
+ if (packageName == null) {
+ getErrPrintWriter().println("Error: package name not specified");
+ return 1;
+ }
+ String volumeUuid = getNextArg();
+ if ("internal".equals(volumeUuid)) {
+ volumeUuid = null;
+ }
+
+ final int moveId = mInterface.movePackage(packageName, volumeUuid);
+
+ int status = mInterface.getMoveStatus(moveId);
+ while (!PackageManager.isMoveStatusFinished(status)) {
+ SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+ status = mInterface.getMoveStatus(moveId);
+ }
+
+ if (status == PackageManager.MOVE_SUCCEEDED) {
+ getOutPrintWriter().println("Success");
+ return 0;
+ } else {
+ getErrPrintWriter().println("Failure [" + status + "]");
+ return 1;
+ }
+ }
+
+ public int runMovePrimaryStorage() throws RemoteException {
+ String volumeUuid = getNextArg();
+ if ("internal".equals(volumeUuid)) {
+ volumeUuid = null;
+ }
+
+ final int moveId = mInterface.movePrimaryStorage(volumeUuid);
+
+ int status = mInterface.getMoveStatus(moveId);
+ while (!PackageManager.isMoveStatusFinished(status)) {
+ SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+ status = mInterface.getMoveStatus(moveId);
+ }
+
+ if (status == PackageManager.MOVE_SUCCEEDED) {
+ getOutPrintWriter().println("Success");
+ return 0;
+ } else {
+ getErrPrintWriter().println("Failure [" + status + "]");
+ return 1;
+ }
+ }
+
+ private int runCompile() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+ boolean forceCompilation = false;
+ boolean allPackages = false;
+ boolean clearProfileData = false;
+ String compilerFilter = null;
+ String compilationReason = null;
+ String checkProfilesRaw = null;
+ boolean secondaryDex = false;
+ String split = null;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-a":
+ allPackages = true;
+ break;
+ case "-c":
+ clearProfileData = true;
+ break;
+ case "-f":
+ forceCompilation = true;
+ break;
+ case "-m":
+ compilerFilter = getNextArgRequired();
+ break;
+ case "-r":
+ compilationReason = getNextArgRequired();
+ break;
+ case "--check-prof":
+ checkProfilesRaw = getNextArgRequired();
+ break;
+ case "--reset":
+ forceCompilation = true;
+ clearProfileData = true;
+ compilationReason = "install";
+ break;
+ case "--secondary-dex":
+ secondaryDex = true;
+ break;
+ case "--split":
+ split = getNextArgRequired();
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ if (checkProfilesRaw != null) {
+ if ("true".equals(checkProfilesRaw)) {
+ checkProfiles = true;
+ } else if ("false".equals(checkProfilesRaw)) {
+ checkProfiles = false;
+ } else {
+ pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\".");
+ return 1;
+ }
+ }
+
+ if (compilerFilter != null && compilationReason != null) {
+ pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " +
+ "at the same time");
+ return 1;
+ }
+ if (compilerFilter == null && compilationReason == null) {
+ pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " +
+ "reason (\"-r\") at the same time");
+ return 1;
+ }
+
+ if (allPackages && split != null) {
+ pw.println("-a cannot be specified together with --split");
+ return 1;
+ }
+
+ if (secondaryDex && split != null) {
+ pw.println("--secondary-dex cannot be specified together with --split");
+ return 1;
+ }
+
+ String targetCompilerFilter;
+ if (compilerFilter != null) {
+ if (!DexFile.isValidCompilerFilter(compilerFilter)) {
+ pw.println("Error: \"" + compilerFilter +
+ "\" is not a valid compilation filter.");
+ return 1;
+ }
+ targetCompilerFilter = compilerFilter;
+ } else {
+ int reason = -1;
+ for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
+ if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals(
+ compilationReason)) {
+ reason = i;
+ break;
+ }
+ }
+ if (reason == -1) {
+ pw.println("Error: Unknown compilation reason: " + compilationReason);
+ return 1;
+ }
+ targetCompilerFilter =
+ PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason);
+ }
+
+
+ List<String> packageNames = null;
+ if (allPackages) {
+ packageNames = mInterface.getAllPackages();
+ } else {
+ String packageName = getNextArg();
+ if (packageName == null) {
+ pw.println("Error: package name not specified");
+ return 1;
+ }
+ packageNames = Collections.singletonList(packageName);
+ }
+
+ List<String> failedPackages = new ArrayList<>();
+ for (String packageName : packageNames) {
+ if (clearProfileData) {
+ mInterface.clearApplicationProfileData(packageName);
+ }
+
+ boolean result = secondaryDex
+ ? mInterface.performDexOptSecondary(packageName,
+ targetCompilerFilter, forceCompilation)
+ : mInterface.performDexOptMode(packageName,
+ checkProfiles, targetCompilerFilter, forceCompilation,
+ true /* bootComplete */, split);
+ if (!result) {
+ failedPackages.add(packageName);
+ }
+ }
+
+ if (failedPackages.isEmpty()) {
+ pw.println("Success");
+ return 0;
+ } else if (failedPackages.size() == 1) {
+ pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled");
+ return 1;
+ } else {
+ pw.print("Failure: the following packages could not be compiled: ");
+ boolean is_first = true;
+ for (String packageName : failedPackages) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ pw.print(", ");
+ }
+ pw.print(packageName);
+ }
+ pw.println();
+ return 1;
+ }
+ }
+
+ private int runreconcileSecondaryDexFiles() throws RemoteException {
+ String packageName = getNextArg();
+ mInterface.reconcileSecondaryDexFiles(packageName);
+ return 0;
+ }
+
+ public int runForceDexOpt() throws RemoteException {
+ mInterface.forceDexOpt(getNextArgRequired());
+ return 0;
+ }
+
+ private int runDexoptJob() throws RemoteException {
+ boolean result = mInterface.runBackgroundDexoptJob();
+ return result ? 0 : -1;
+ }
+
+ private int runDumpProfiles() throws RemoteException {
+ String packageName = getNextArg();
+ mInterface.dumpProfiles(packageName);
+ return 0;
+ }
+
+ private int runUninstall() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int flags = 0;
+ int userId = UserHandle.USER_ALL;
+ int versionCode = PackageManager.VERSION_CODE_HIGHEST;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-k":
+ flags |= PackageManager.DELETE_KEEP_DATA;
+ break;
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ case "--versionCode":
+ versionCode = Integer.parseInt(getNextArgRequired());
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ final String packageName = getNextArg();
+ if (packageName == null) {
+ pw.println("Error: package name not specified");
+ return 1;
+ }
+
+ // if a split is specified, just remove it and not the whole package
+ final String splitName = getNextArg();
+ if (splitName != null) {
+ return runRemoveSplit(packageName, splitName);
+ }
+
+ userId = translateUserId(userId, "runUninstall");
+ if (userId == UserHandle.USER_ALL) {
+ userId = UserHandle.USER_SYSTEM;
+ flags |= PackageManager.DELETE_ALL_USERS;
+ } else {
+ final PackageInfo info = mInterface.getPackageInfo(packageName,
+ PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
+ if (info == null) {
+ pw.println("Failure [not installed for " + userId + "]");
+ return 1;
+ }
+ final boolean isSystem =
+ (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ // If we are being asked to delete a system app for just one
+ // user set flag so it disables rather than reverting to system
+ // version of the app.
+ if (isSystem) {
+ flags |= PackageManager.DELETE_SYSTEM_APP;
+ }
+ }
+
+ final LocalIntentReceiver receiver = new LocalIntentReceiver();
+ mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName,
+ versionCode), null /*callerPackageName*/, flags,
+ receiver.getIntentSender(), userId);
+
+ final Intent result = receiver.getResult();
+ final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ pw.println("Success");
+ return 0;
+ } else {
+ pw.println("Failure ["
+ + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+ return 1;
+ }
+ }
+
+ private int runRemoveSplit(String packageName, String splitName) throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
+ sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+ sessionParams.appPackageName = packageName;
+ final int sessionId =
+ doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL);
+ boolean abandonSession = true;
+ try {
+ if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/)
+ != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
+ if (doCommitSession(sessionId, false /*logSuccess*/)
+ != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
+ abandonSession = false;
+ pw.println("Success");
+ return 0;
+ } finally {
+ if (abandonSession) {
+ try {
+ doAbandonSession(sessionId, false /*logSuccess*/);
+ } catch (Exception ignore) {
+ }
+ }
+ }
+ }
+
+ static class ClearDataObserver extends IPackageDataObserver.Stub {
+ boolean finished;
+ boolean result;
+
+ @Override
+ public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
+ synchronized (this) {
+ finished = true;
+ result = succeeded;
+ notifyAll();
+ }
+ }
+ }
+
+ private int runClear() throws RemoteException {
+ int userId = UserHandle.USER_SYSTEM;
+ String option = getNextOption();
+ if (option != null && option.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ }
+
+ String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified");
+ return 1;
+ }
+
+ ClearDataObserver obs = new ClearDataObserver();
+ ActivityManager.getService().clearApplicationUserData(pkg, obs, userId);
+ synchronized (obs) {
+ while (!obs.finished) {
+ try {
+ obs.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ if (obs.result) {
+ getOutPrintWriter().println("Success");
+ return 0;
+ } else {
+ getErrPrintWriter().println("Failed");
+ return 1;
+ }
+ }
+
+ private static String enabledSettingToString(int state) {
+ switch (state) {
+ case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
+ return "default";
+ case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
+ return "enabled";
+ case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+ return "disabled";
+ case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
+ return "disabled-user";
+ case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+ return "disabled-until-used";
+ }
+ return "unknown";
+ }
+
+ private int runSetEnabledSetting(int state) throws RemoteException {
+ int userId = UserHandle.USER_SYSTEM;
+ String option = getNextOption();
+ if (option != null && option.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ }
+
+ String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package or component specified");
+ return 1;
+ }
+ ComponentName cn = ComponentName.unflattenFromString(pkg);
+ if (cn == null) {
+ mInterface.setApplicationEnabledSetting(pkg, state, 0, userId,
+ "shell:" + android.os.Process.myUid());
+ getOutPrintWriter().println("Package " + pkg + " new state: "
+ + enabledSettingToString(
+ mInterface.getApplicationEnabledSetting(pkg, userId)));
+ return 0;
+ } else {
+ mInterface.setComponentEnabledSetting(cn, state, 0, userId);
+ getOutPrintWriter().println("Component " + cn.toShortString() + " new state: "
+ + enabledSettingToString(
+ mInterface.getComponentEnabledSetting(cn, userId)));
+ return 0;
+ }
+ }
+
+ private int runSetHiddenSetting(boolean state) throws RemoteException {
+ int userId = UserHandle.USER_SYSTEM;
+ String option = getNextOption();
+ if (option != null && option.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ }
+
+ String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package or component specified");
+ return 1;
+ }
+ mInterface.setApplicationHiddenSettingAsUser(pkg, state, userId);
+ getOutPrintWriter().println("Package " + pkg + " new hidden state: "
+ + mInterface.getApplicationHiddenSettingAsUser(pkg, userId));
+ return 0;
+ }
+
+ private int runSuspend(boolean suspendedState) {
+ final PrintWriter pw = getOutPrintWriter();
+ int userId = UserHandle.USER_SYSTEM;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ String packageName = getNextArg();
+ if (packageName == null) {
+ pw.println("Error: package name not specified");
+ return 1;
+ }
+
+ try {
+ mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
+ userId);
+ pw.println("Package " + packageName + " new suspended state: "
+ + mInterface.isPackageSuspendedForUser(packageName, userId));
+ return 0;
+ } catch (RemoteException | IllegalArgumentException e) {
+ pw.println(e.toString());
+ return 1;
+ }
+ }
+
+ private int runGrantRevokePermission(boolean grant) throws RemoteException {
+ int userId = UserHandle.USER_SYSTEM;
+
+ String opt = null;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ }
+ }
+
+ String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified");
+ return 1;
+ }
+ String perm = getNextArg();
+ if (perm == null) {
+ getErrPrintWriter().println("Error: no permission specified");
+ return 1;
+ }
+
+ if (grant) {
+ mInterface.grantRuntimePermission(pkg, perm, userId);
+ } else {
+ mInterface.revokeRuntimePermission(pkg, perm, userId);
+ }
+ return 0;
+ }
+
+ private int runResetPermissions() throws RemoteException {
+ mInterface.resetRuntimePermissions();
+ return 0;
+ }
+
+ private int runSetPermissionEnforced() throws RemoteException {
+ final String permission = getNextArg();
+ if (permission == null) {
+ getErrPrintWriter().println("Error: no permission specified");
+ return 1;
+ }
+ final String enforcedRaw = getNextArg();
+ if (enforcedRaw == null) {
+ getErrPrintWriter().println("Error: no enforcement specified");
+ return 1;
+ }
+ mInterface.setPermissionEnforced(permission, Boolean.parseBoolean(enforcedRaw));
+ return 0;
+ }
+
+ private int runGetPrivappPermissions() {
+ final String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified.");
+ return 1;
+ }
+ ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg);
+ getOutPrintWriter().println(privAppPermissions == null
+ ? "{}" : privAppPermissions.toString());
+ return 0;
+ }
+
+ private int runGetPrivappDenyPermissions() {
+ final String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified.");
+ return 1;
+ }
+ ArraySet<String> privAppDenyPermissions =
+ SystemConfig.getInstance().getPrivAppDenyPermissions(pkg);
+ getOutPrintWriter().println(privAppDenyPermissions == null
+ ? "{}" : privAppDenyPermissions.toString());
+ return 0;
+ }
+
+ private int runGetOemPermissions() {
+ final String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified.");
+ return 1;
+ }
+ final Map<String, Boolean> oemPermissions = SystemConfig.getInstance()
+ .getOemPermissions(pkg);
+ if (oemPermissions == null || oemPermissions.isEmpty()) {
+ getOutPrintWriter().println("{}");
+ } else {
+ oemPermissions.forEach((permission, granted) ->
+ getOutPrintWriter().println(permission + " granted:" + granted)
+ );
+ }
+ return 0;
+ }
+
+ private String linkStateToString(int state) {
+ switch (state) {
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined";
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask";
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always";
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never";
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask";
+ }
+ return "Unknown link state: " + state;
+ }
+
+ // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
+ private int runSetAppLink() throws RemoteException {
+ int userId = UserHandle.USER_SYSTEM;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ // Package name to act on; required
+ final String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified.");
+ return 1;
+ }
+
+ // State to apply; {always|ask|never|undefined}, required
+ final String modeString = getNextArg();
+ if (modeString == null) {
+ getErrPrintWriter().println("Error: no app link state specified.");
+ return 1;
+ }
+
+ final int newMode;
+ switch (modeString.toLowerCase()) {
+ case "undefined":
+ newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ break;
+
+ case "always":
+ newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+ break;
+
+ case "ask":
+ newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+ break;
+
+ case "always-ask":
+ newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
+ break;
+
+ case "never":
+ newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+ break;
+
+ default:
+ getErrPrintWriter().println("Error: unknown app link state '" + modeString + "'");
+ return 1;
+ }
+
+ final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId);
+ if (info == null) {
+ getErrPrintWriter().println("Error: package " + pkg + " not found.");
+ return 1;
+ }
+
+ if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
+ getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
+ return 1;
+ }
+
+ if (!mInterface.updateIntentVerificationStatus(pkg, newMode, userId)) {
+ getErrPrintWriter().println("Error: unable to update app link status for " + pkg);
+ return 1;
+ }
+
+ return 0;
+ }
+
+ // pm get-app-link [--user USER_ID] PACKAGE
+ private int runGetAppLink() throws RemoteException {
+ int userId = UserHandle.USER_SYSTEM;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ // Package name to act on; required
+ final String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified.");
+ return 1;
+ }
+
+ final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId);
+ if (info == null) {
+ getErrPrintWriter().println("Error: package " + pkg + " not found.");
+ return 1;
+ }
+
+ if ((info.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
+ getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
+ return 1;
+ }
+
+ getOutPrintWriter().println(linkStateToString(
+ mInterface.getIntentVerificationStatus(pkg, userId)));
+
+ return 0;
+ }
+
+ private int runTrimCaches() throws RemoteException {
+ String size = getNextArg();
+ if (size == null) {
+ getErrPrintWriter().println("Error: no size specified");
+ return 1;
+ }
+ long multiplier = 1;
+ int len = size.length();
+ char c = size.charAt(len - 1);
+ if (c < '0' || c > '9') {
+ if (c == 'K' || c == 'k') {
+ multiplier = 1024L;
+ } else if (c == 'M' || c == 'm') {
+ multiplier = 1024L*1024L;
+ } else if (c == 'G' || c == 'g') {
+ multiplier = 1024L*1024L*1024L;
+ } else {
+ getErrPrintWriter().println("Invalid suffix: " + c);
+ return 1;
+ }
+ size = size.substring(0, len-1);
+ }
+ long sizeVal;
+ try {
+ sizeVal = Long.parseLong(size) * multiplier;
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: expected number at: " + size);
+ return 1;
+ }
+ String volumeUuid = getNextArg();
+ if ("internal".equals(volumeUuid)) {
+ volumeUuid = null;
+ }
+ ClearDataObserver obs = new ClearDataObserver();
+ mInterface.freeStorageAndNotify(volumeUuid, sizeVal,
+ StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs);
+ synchronized (obs) {
+ while (!obs.finished) {
+ try {
+ obs.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ return 0;
+ }
+
+ private static boolean isNumber(String s) {
+ try {
+ Integer.parseInt(s);
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ return true;
+ }
+
+ public int runCreateUser() throws RemoteException {
+ String name;
+ int userId = -1;
+ int flags = 0;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if ("--profileOf".equals(opt)) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else if ("--managed".equals(opt)) {
+ flags |= UserInfo.FLAG_MANAGED_PROFILE;
+ } else if ("--restricted".equals(opt)) {
+ flags |= UserInfo.FLAG_RESTRICTED;
+ } else if ("--ephemeral".equals(opt)) {
+ flags |= UserInfo.FLAG_EPHEMERAL;
+ } else if ("--guest".equals(opt)) {
+ flags |= UserInfo.FLAG_GUEST;
+ } else if ("--demo".equals(opt)) {
+ flags |= UserInfo.FLAG_DEMO;
+ } else {
+ getErrPrintWriter().println("Error: unknown option " + opt);
+ return 1;
+ }
+ }
+ String arg = getNextArg();
+ if (arg == null) {
+ getErrPrintWriter().println("Error: no user name specified.");
+ return 1;
+ }
+ name = arg;
+ UserInfo info;
+ IUserManager um = IUserManager.Stub.asInterface(
+ ServiceManager.getService(Context.USER_SERVICE));
+ IAccountManager accm = IAccountManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCOUNT_SERVICE));
+ if ((flags & UserInfo.FLAG_RESTRICTED) != 0) {
+ // In non-split user mode, userId can only be SYSTEM
+ int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
+ info = um.createRestrictedProfile(name, parentUserId);
+ accm.addSharedAccountsFromParentUser(parentUserId, userId,
+ (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
+ } else if (userId < 0) {
+ info = um.createUser(name, flags);
+ } else {
+ info = um.createProfileForUser(name, flags, userId, null);
+ }
+
+ if (info != null) {
+ getOutPrintWriter().println("Success: created user id " + info.id);
+ return 0;
+ } else {
+ getErrPrintWriter().println("Error: couldn't create User.");
+ return 1;
+ }
+ }
+
+ public int runRemoveUser() throws RemoteException {
+ int userId;
+ String arg = getNextArg();
+ if (arg == null) {
+ getErrPrintWriter().println("Error: no user id specified.");
+ return 1;
+ }
+ userId = UserHandle.parseUserArg(arg);
+ IUserManager um = IUserManager.Stub.asInterface(
+ ServiceManager.getService(Context.USER_SERVICE));
+ if (um.removeUser(userId)) {
+ getOutPrintWriter().println("Success: removed user");
+ return 0;
+ } else {
+ getErrPrintWriter().println("Error: couldn't remove user id " + userId);
+ return 1;
+ }
+ }
+
+ public int runSetUserRestriction() throws RemoteException {
+ int userId = UserHandle.USER_SYSTEM;
+ String opt = getNextOption();
+ if (opt != null && "--user".equals(opt)) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ }
+
+ String restriction = getNextArg();
+ String arg = getNextArg();
+ boolean value;
+ if ("1".equals(arg)) {
+ value = true;
+ } else if ("0".equals(arg)) {
+ value = false;
+ } else {
+ getErrPrintWriter().println("Error: valid value not specified");
+ return 1;
+ }
+ IUserManager um = IUserManager.Stub.asInterface(
+ ServiceManager.getService(Context.USER_SERVICE));
+ um.setUserRestriction(restriction, value, userId);
+ return 0;
+ }
+
+ public int runGetMaxUsers() {
+ getOutPrintWriter().println("Maximum supported users: "
+ + UserManager.getMaxSupportedUsers());
+ return 0;
+ }
+
private static class InstallParams {
SessionParams sessionParams;
String installerPackageName;
@@ -1210,6 +1909,12 @@
throw new IllegalArgumentException("Missing inherit package name");
}
break;
+ case "--pkg":
+ sessionParams.appPackageName = getNextArg();
+ if (sessionParams.appPackageName == null) {
+ throw new IllegalArgumentException("Missing package name");
+ }
+ break;
case "-S":
final long sizeBytes = Long.parseLong(getNextArg());
if (sizeBytes <= 0) {
@@ -1221,6 +1926,7 @@
sessionParams.abiOverride = checkAbiArgument(getNextArg());
break;
case "--ephemeral":
+ case "--instant":
case "--instantapp":
sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
break;
@@ -1287,46 +1993,17 @@
}
}
- private int runGetPrivappPermissions() {
- final String pkg = getNextArg();
- if (pkg == null) {
- System.err.println("Error: no package specified.");
- return 1;
- }
- ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg);
- getOutPrintWriter().println(privAppPermissions == null
- ? "{}" : privAppPermissions.toString());
- return 0;
- }
+ private int runSetInstaller() throws RemoteException {
+ final String targetPackage = getNextArg();
+ final String installerPackageName = getNextArg();
- private int runGetPrivappDenyPermissions() {
- final String pkg = getNextArg();
- if (pkg == null) {
- System.err.println("Error: no package specified.");
+ if (targetPackage == null || installerPackageName == null) {
+ getErrPrintWriter().println("Must provide both target and installer package names");
return 1;
}
- ArraySet<String> privAppDenyPermissions =
- SystemConfig.getInstance().getPrivAppDenyPermissions(pkg);
- getOutPrintWriter().println(privAppDenyPermissions == null
- ? "{}" : privAppDenyPermissions.toString());
- return 0;
- }
- private int runGetOemPermissions() {
- final String pkg = getNextArg();
- if (pkg == null) {
- System.err.println("Error: no package specified.");
- return 1;
- }
- final Map<String, Boolean> oemPermissions = SystemConfig.getInstance()
- .getOemPermissions(pkg);
- if (oemPermissions == null || oemPermissions.isEmpty()) {
- getOutPrintWriter().println("{}");
- } else {
- oemPermissions.forEach((permission, granted) ->
- getOutPrintWriter().println(permission + " granted:" + granted)
- );
- }
+ mInterface.setInstallerPackageName(targetPackage, installerPackageName);
+ getOutPrintWriter().println("Success");
return 0;
}
@@ -1367,6 +2044,16 @@
}
}
+ private int runDump() {
+ String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified");
+ return 1;
+ }
+ ActivityManager.dumpPackageStateStatic(getOutFileDescriptor(), pkg);
+ return 0;
+ }
+
private static String checkAbiArgument(String abi) {
if (TextUtils.isEmpty(abi)) {
throw new IllegalArgumentException("Missing ABI argument");
@@ -1407,20 +2094,24 @@
private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
boolean logSuccess) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
- if (FORCE_STREAM_INSTALL && inPath != null && !STDIN_PATH.equals(inPath)) {
- pw.println("Error: APK content must be streamed");
- return 1;
- }
+ final ParcelFileDescriptor fd;
if (STDIN_PATH.equals(inPath)) {
- inPath = null;
+ fd = null;
} else if (inPath != null) {
- final File file = new File(inPath);
- if (file.isFile()) {
- sizeBytes = file.length();
+ fd = openFileForSystem(inPath, "r");
+ if (fd == null) {
+ return -1;
}
+ sizeBytes = fd.getStatSize();
+ if (sizeBytes < 0) {
+ getErrPrintWriter().println("Unable to get size of: " + inPath);
+ return -1;
+ }
+ } else {
+ fd = null;
}
if (sizeBytes <= 0) {
- pw.println("Error: must specify a APK size");
+ getErrPrintWriter().println("Error: must specify a APK size");
return 1;
}
@@ -1433,8 +2124,8 @@
session = new PackageInstaller.Session(
mInterface.getPackageInstaller().openSession(sessionId));
- if (inPath != null) {
- in = new FileInputStream(inPath);
+ if (fd != null) {
+ in = new ParcelFileDescriptor.AutoCloseInputStream(fd);
} else {
in = new SizedInputStream(getRawInputStream(), sizeBytes);
}
@@ -1459,7 +2150,7 @@
}
return 0;
} catch (IOException e) {
- pw.println("Error: failed to write; " + e.getMessage());
+ getErrPrintWriter().println("Error: failed to write; " + e.getMessage());
return 1;
} finally {
IoUtils.closeQuietly(out);
@@ -1663,10 +2354,203 @@
pw.println(" help");
pw.println(" Print this help text.");
pw.println("");
+ pw.println(" path [--user USER_ID] PACKAGE");
+ pw.println(" Print the path to the .apk of the given PACKAGE.");
+ pw.println("");
+ pw.println(" dump PACKAGE");
+ pw.println(" Print various system state associated with the given PACKAGE.");
+ pw.println("");
+ pw.println(" list features");
+ pw.println(" Prints all features of the system.");
+ pw.println("");
+ pw.println(" has-feature FEATURE_NAME [version]");
+ pw.println(" Prints true and returns exit status 0 when system has a FEATURE_NAME,");
+ pw.println(" otherwise prints false and returns exit status 1");
+ pw.println("");
+ pw.println(" list instrumentation [-f] [TARGET-PACKAGE]");
+ pw.println(" Prints all test packages; optionally only those targeting TARGET-PACKAGE");
+ pw.println(" Options:");
+ pw.println(" -f: dump the name of the .apk file containing the test package");
+ pw.println("");
+ pw.println(" list libraries");
+ pw.println(" Prints all system libraries.");
+ pw.println("");
+ pw.println(" list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] ");
+ pw.println(" [--uid UID] [--user USER_ID] [FILTER]");
+ pw.println(" Prints all packages; optionally only those whose name contains");
+ pw.println(" the text in FILTER. Options are:");
+ pw.println(" -f: see their associated file");
+ pw.println(" -d: filter to only show disabled packages");
+ pw.println(" -e: filter to only show enabled packages");
+ pw.println(" -s: filter to only show system packages");
+ pw.println(" -3: filter to only show third party packages");
+ pw.println(" -i: see the installer for the packages");
+ pw.println(" -l: ignored (used for compatibility with older releases)");
+ pw.println(" -U: also show the package UID");
+ pw.println(" -u: also include uninstalled packages");
+ pw.println(" --uid UID: filter to only show packages with the given UID");
+ pw.println(" --user USER_ID: only list packages belonging to the given user");
+ pw.println("");
+ pw.println(" list permission-groups");
+ pw.println(" Prints all known permission groups.");
+ pw.println("");
+ pw.println(" list permissions [-g] [-f] [-d] [-u] [GROUP]");
+ pw.println(" Prints all known permissions; optionally only those in GROUP. Options are:");
+ pw.println(" -g: organize by group");
+ pw.println(" -f: print all information");
+ pw.println(" -s: short summary");
+ pw.println(" -d: only list dangerous permissions");
+ pw.println(" -u: list only the permissions users will see");
+ pw.println("");
+ pw.println(" resolve-activity [--brief] [--components] [--user USER_ID] INTENT");
+ pw.println(" Prints the activity that resolves to the given INTENT.");
+ pw.println("");
+ pw.println(" query-activities [--brief] [--components] [--user USER_ID] INTENT");
+ pw.println(" Prints all activities that can handle the given INTENT.");
+ pw.println("");
+ pw.println(" query-services [--brief] [--components] [--user USER_ID] INTENT");
+ pw.println(" Prints all services that can handle the given INTENT.");
+ pw.println("");
+ pw.println(" query-receivers [--brief] [--components] [--user USER_ID] INTENT");
+ pw.println(" Prints all broadcast receivers that can handle the given INTENT.");
+ pw.println("");
+ pw.println(" install [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]");
+ pw.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+ pw.println(" [--originating-uri URI] [---referrer URI]");
+ pw.println(" [--abi ABI_NAME] [--force-sdk]");
+ pw.println(" [--preload] [--instantapp] [--full] [--dont-kill]");
+ pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
+ pw.println(" Install an application. Must provide the apk data to install, either as a");
+ pw.println(" file path or '-' to read from stdin. Options are:");
+ pw.println(" -l: forward lock application");
+ pw.println(" -r: allow replacement of existing application");
+ pw.println(" -t: allow test packages");
+ pw.println(" -i: specify package name of installer owning the app");
+ pw.println(" -s: install application on sdcard");
+ pw.println(" -f: install application on internal flash");
+ pw.println(" -d: allow version code downgrade (debuggable packages only)");
+ pw.println(" -p: partial application install (new split on top of existing pkg)");
+ pw.println(" -g: grant all runtime permissions");
+ pw.println(" -S: size in bytes of package, required for stdin");
+ pw.println(" --user: install under the given user.");
+ pw.println(" --dont-kill: installing a new feature split, don't kill running app");
+ pw.println(" --originating-uri: set URI where app was downloaded from");
+ pw.println(" --referrer: set URI that instigated the install of the app");
+ pw.println(" --pkg: specify expected package name of app being installed");
+ pw.println(" --abi: override the default ABI of the platform");
+ pw.println(" --instantapp: cause the app to be installed as an ephemeral install app");
+ pw.println(" --full: cause the app to be installed as a non-ephemeral full app");
+ pw.println(" --install-location: force the install location:");
+ pw.println(" 0=auto, 1=internal only, 2=prefer external");
+ pw.println(" --force-uuid: force install on to disk volume with given UUID");
+ pw.println(" --force-sdk: allow install even when existing app targets platform");
+ pw.println(" codename but new one targets a final API level");
+ pw.println("");
+ pw.println(" install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]");
+ pw.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+ pw.println(" [--originating-uri URI] [---referrer URI]");
+ pw.println(" [--abi ABI_NAME] [--force-sdk]");
+ pw.println(" [--preload] [--instantapp] [--full] [--dont-kill]");
+ pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
+ pw.println(" Like \"install\", but starts an install session. Use \"install-write\"");
+ pw.println(" to push data into the session, and \"install-commit\" to finish.");
+ pw.println("");
+ pw.println(" install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
+ pw.println(" Write an apk into the given install session. If the path is '-', data");
+ pw.println(" will be read from stdin. Options are:");
+ pw.println(" -S: size in bytes of package, required for stdin");
+ pw.println("");
+ pw.println(" install-commit SESSION_ID");
+ pw.println(" Commit the given active install session, installing the app.");
+ pw.println("");
+ pw.println(" install-abandon SESSION_ID");
+ pw.println(" Delete the given active install session.");
+ pw.println("");
+ pw.println(" set-install-location LOCATION");
+ pw.println(" Changes the default install location. NOTE this is only intended for debugging;");
+ pw.println(" using this can cause applications to break and other undersireable behavior.");
+ pw.println(" LOCATION is one of:");
+ pw.println(" 0 [auto]: Let system decide the best location");
+ pw.println(" 1 [internal]: Install on internal device storage");
+ pw.println(" 2 [external]: Install on external media");
+ pw.println("");
+ pw.println(" get-install-location");
+ pw.println(" Returns the current install location: 0, 1 or 2 as per set-install-location.");
+ pw.println("");
+ pw.println(" move-package PACKAGE [internal|UUID]");
+ pw.println("");
+ pw.println(" move-primary-storage [internal|UUID]");
+ pw.println("");
+ pw.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE [SPLIT]");
+ pw.println(" Remove the given package name from the system. May remove an entire app");
+ pw.println(" if no SPLIT name is specified, otherwise will remove only the split of the");
+ pw.println(" given app. Options are:");
+ pw.println(" -k: keep the data and cache directories around after package removal.");
+ pw.println(" --user: remove the app from the given user.");
+ pw.println(" --versionCode: only uninstall if the app has the given version code.");
+ pw.println("");
+ pw.println(" clear [--user USER_ID] PACKAGE");
+ pw.println(" Deletes all data associated with a package.");
+ pw.println("");
+ pw.println(" enable [--user USER_ID] PACKAGE_OR_COMPONENT");
+ pw.println(" disable [--user USER_ID] PACKAGE_OR_COMPONENT");
+ pw.println(" disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
+ pw.println(" disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
+ pw.println(" default-state [--user USER_ID] PACKAGE_OR_COMPONENT");
+ pw.println(" These commands change the enabled state of a given package or");
+ pw.println(" component (written as \"package/class\").");
+ pw.println("");
+ pw.println(" hide [--user USER_ID] PACKAGE_OR_COMPONENT");
+ pw.println(" unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
+ pw.println("");
+ pw.println(" suspend [--user USER_ID] TARGET-PACKAGE");
+ pw.println(" Suspends the specified package (as user).");
+ pw.println("");
+ pw.println(" unsuspend [--user USER_ID] TARGET-PACKAGE");
+ pw.println(" Unsuspends the specified package (as user).");
+ pw.println("");
+ pw.println(" grant [--user USER_ID] PACKAGE PERMISSION");
+ pw.println(" revoke [--user USER_ID] PACKAGE PERMISSION");
+ pw.println(" These commands either grant or revoke permissions to apps. The permissions");
+ pw.println(" must be declared as used in the app's manifest, be runtime permissions");
+ pw.println(" (protection level dangerous), and the app targeting SDK greater than Lollipop MR1.");
+ pw.println("");
+ pw.println(" reset-permissions");
+ pw.println(" Revert all runtime permissions to their default state.");
+ pw.println("");
+ pw.println(" set-permission-enforced PERMISSION [true|false]");
+ pw.println("");
+ pw.println(" get-privapp-permissions TARGET-PACKAGE");
+ pw.println(" Prints all privileged permissions for a package.");
+ pw.println("");
+ pw.println(" get-privapp-deny-permissions TARGET-PACKAGE");
+ pw.println(" Prints all privileged permissions that are denied for a package.");
+ pw.println("");
+ pw.println(" get-oem-permissions TARGET-PACKAGE");
+ pw.println(" Prints all OEM permissions for a package.");
+ pw.println("");
+ pw.println(" set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}");
+ pw.println(" get-app-link [--user USER_ID] PACKAGE");
+ pw.println("");
+ pw.println(" trim-caches DESIRED_FREE_SPACE [internal|UUID]");
+ pw.println(" Trim cache files to reach the given free space.");
+ pw.println("");
+ pw.println(" create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]");
+ pw.println(" [--guest] USER_NAME");
+ pw.println(" Create a new user with the given USER_NAME, printing the new user identifier");
+ pw.println(" of the user.");
+ pw.println("");
+ pw.println(" remove-user USER_ID");
+ pw.println(" Remove the user with the given USER_IDENTIFIER, deleting all data");
+ pw.println(" associated with that user");
+ pw.println("");
+ pw.println(" set-user-restriction [--user USER_ID] RESTRICTION VALUE");
+ pw.println("");
+ pw.println(" get-max-users");
+ pw.println("");
pw.println(" compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]");
pw.println(" [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)");
- pw.println(" Trigger compilation of TARGET-PACKAGE or all packages if \"-a\".");
- pw.println(" Options:");
+ pw.println(" Trigger compilation of TARGET-PACKAGE or all packages if \"-a\". Options are:");
pw.println(" -a: compile all packages");
pw.println(" -c: clear profile data before compiling");
pw.println(" -f: force compilation even if not needed");
@@ -1690,72 +2574,32 @@
pw.println(" --check-prof (true | false): look at profiles when doing dexopt?");
pw.println(" --secondary-dex: compile app secondary dex files");
pw.println(" --split SPLIT: compile only the given split name");
+ pw.println("");
+ pw.println(" force-dex-opt PACKAGE");
+ pw.println(" Force immediate execution of dex opt for the given PACKAGE.");
+ pw.println("");
pw.println(" bg-dexopt-job");
pw.println(" Execute the background optimizations immediately.");
pw.println(" Note that the command only runs the background optimizer logic. It may");
pw.println(" overlap with the actual job but the job scheduler will not be able to");
pw.println(" cancel it. It will also run even if the device is not in the idle");
pw.println(" maintenance mode.");
- pw.println(" list features");
- pw.println(" Prints all features of the system.");
- pw.println(" list instrumentation [-f] [TARGET-PACKAGE]");
- pw.println(" Prints all test packages; optionally only those targeting TARGET-PACKAGE");
- pw.println(" Options:");
- pw.println(" -f: dump the name of the .apk file containing the test package");
- pw.println(" list libraries");
- pw.println(" Prints all system libraries.");
- pw.println(" list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] "
- + "[--uid UID] [--user USER_ID] [FILTER]");
- pw.println(" Prints all packages; optionally only those whose name contains");
- pw.println(" the text in FILTER.");
- pw.println(" Options:");
- pw.println(" -f: see their associated file");
- pw.println(" -d: filter to only show disabled packages");
- pw.println(" -e: filter to only show enabled packages");
- pw.println(" -s: filter to only show system packages");
- pw.println(" -3: filter to only show third party packages");
- pw.println(" -i: see the installer for the packages");
- pw.println(" -l: ignored (used for compatibility with older releases)");
- pw.println(" -U: also show the package UID");
- pw.println(" -u: also include uninstalled packages");
- pw.println(" --uid UID: filter to only show packages with the given UID");
- pw.println(" --user USER_ID: only list packages belonging to the given user");
+ pw.println("");
pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE");
pw.println(" Reconciles the package secondary dex files with the generated oat files.");
- pw.println(" list permission-groups");
- pw.println(" Prints all known permission groups.");
- pw.println(" list permissions [-g] [-f] [-d] [-u] [GROUP]");
- pw.println(" Prints all known permissions; optionally only those in GROUP.");
- pw.println(" Options:");
- pw.println(" -g: organize by group");
- pw.println(" -f: print all information");
- pw.println(" -s: short summary");
- pw.println(" -d: only list dangerous permissions");
- pw.println(" -u: list only the permissions users will see");
+ pw.println("");
pw.println(" dump-profiles TARGET-PACKAGE");
pw.println(" Dumps method/class profile files to");
pw.println(" /data/misc/profman/TARGET-PACKAGE.txt");
- pw.println(" resolve-activity [--brief] [--components] [--user USER_ID] INTENT");
- pw.println(" Prints the activity that resolves to the given Intent.");
- pw.println(" query-activities [--brief] [--components] [--user USER_ID] INTENT");
- pw.println(" Prints all activities that can handle the given Intent.");
- pw.println(" query-services [--brief] [--components] [--user USER_ID] INTENT");
- pw.println(" Prints all services that can handle the given Intent.");
- pw.println(" query-receivers [--brief] [--components] [--user USER_ID] INTENT");
- pw.println(" Prints all broadcast receivers that can handle the given Intent.");
- pw.println(" suspend [--user USER_ID] TARGET-PACKAGE");
- pw.println(" Suspends the specified package (as user).");
- pw.println(" unsuspend [--user USER_ID] TARGET-PACKAGE");
- pw.println(" Unsuspends the specified package (as user).");
+ pw.println("");
pw.println(" set-home-activity [--user USER_ID] TARGET-COMPONENT");
- pw.println(" set the default home activity (aka launcher).");
- pw.println(" has-feature FEATURE_NAME [version]");
- pw.println(" prints true and returns exit status 0 when system has a FEATURE_NAME,");
- pw.println(" otherwise prints false and returns exit status 1");
- pw.println(" get-privileged-permissions TARGET-PACKAGE");
- pw.println(" prints all privileged permissions for a package.");
- pw.println(" get-oem-permissions TARGET-PACKAGE");
- pw.println(" prints all OEM permissions for a package.");
+ pw.println(" Set the default home activity (aka launcher).");
+ pw.println("");
+ pw.println(" set-installer PACKAGE INSTALLER");
+ pw.println(" Set installer package name");
+ pw.println("");
+ pw.println(" get-instantapp-resolver");
+ pw.println(" Return the name of the component that is the current instant app installer.");
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 83cb2db2..3b414e9 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -86,6 +86,10 @@
return sharedUserId;
}
+ public SharedUserSetting getSharedUser() {
+ return sharedUser;
+ }
+
@Override
public String toString() {
return "PackageSetting{"
@@ -120,6 +124,14 @@
return appId;
}
+ public void setInstallPermissionsFixed(boolean fixed) {
+ installPermissionsFixed = fixed;
+ }
+
+ public boolean areInstallPermissionsFixed() {
+ return installPermissionsFixed;
+ }
+
public boolean isPrivileged() {
return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
@@ -136,6 +148,10 @@
return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
+ public boolean isUpdatedSystem() {
+ return (pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ }
+
@Override
public boolean isSharedUser() {
return sharedUser != null;
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index e19e83f..a838768 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -24,6 +24,7 @@
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageUserState;
+import android.content.pm.Signature;
import android.service.pm.PackageProto;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -57,7 +58,7 @@
static final int PKG_INSTALL_COMPLETE = 1;
static final int PKG_INSTALL_INCOMPLETE = 0;
- final String name;
+ public final String name;
final String realName;
String parentPackageName;
@@ -231,6 +232,11 @@
public boolean isSharedUser() {
return false;
}
+
+ public Signature[] getSignatures() {
+ return signatures.mSignatures;
+ }
+
/**
* Makes a shallow copy of the given package settings.
*
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 191b43a..7077fd1 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -544,7 +544,7 @@
}
final PackageSetting dp = mDisabledSysPackages.get(name);
// always make sure the system package code and resource paths dont change
- if (dp == null && p.pkg != null && p.pkg.isSystemApp() && !p.pkg.isUpdatedSystemApp()) {
+ if (dp == null && p.pkg != null && p.pkg.isSystem() && !p.pkg.isUpdatedSystemApp()) {
if((p.pkg != null) && (p.pkg.applicationInfo != null)) {
p.pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
}
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index b8b00af..bfe09b8 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -132,11 +132,9 @@
if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId));
FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId));
- FileUtils.deleteContentsAndDir(getDataMiscDeDirectory(userId));
}
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId));
- FileUtils.deleteContentsAndDir(getDataMiscCeDirectory(userId));
}
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index c18a71d..c86122f 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -80,6 +80,7 @@
UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
UserManager.DISALLOW_DEBUGGING_FEATURES,
UserManager.DISALLOW_CONFIG_VPN,
+ UserManager.DISALLOW_CONFIG_DATE_TIME,
UserManager.DISALLOW_CONFIG_TETHERING,
UserManager.DISALLOW_NETWORK_RESET,
UserManager.DISALLOW_FACTORY_RESET,
@@ -157,6 +158,7 @@
private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet(
UserManager.DISALLOW_ADJUST_VOLUME,
UserManager.DISALLOW_BLUETOOTH_SHARING,
+ UserManager.DISALLOW_CONFIG_DATE_TIME,
UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
UserManager.DISALLOW_RUN_IN_BACKGROUND,
UserManager.DISALLOW_UNMUTE_MICROPHONE,
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index 4fa47b5..0966770 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -56,6 +56,9 @@
// actually shared at runtime.
public static final int DEXOPT_AS_SHARED_LIBRARY = 1 << 6;
+ // When set, indicates that dexopt is invoked from the background service.
+ public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
+
// The name of package to optimize.
private final String mPackageName;
@@ -86,7 +89,8 @@
DEXOPT_ONLY_SECONDARY_DEX |
DEXOPT_ONLY_SHARED_DEX |
DEXOPT_DOWNGRADE |
- DEXOPT_AS_SHARED_LIBRARY;
+ DEXOPT_AS_SHARED_LIBRARY |
+ DEXOPT_IDLE_BACKGROUND_JOB;
if ((flags & (~validityMask)) != 0) {
throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags));
}
@@ -133,6 +137,10 @@
return (mFlags & DEXOPT_AS_SHARED_LIBRARY) != 0;
}
+ public boolean isDexoptIdleBackgroundJob() {
+ return (mFlags & DEXOPT_IDLE_BACKGROUND_JOB) != 0;
+ }
+
public String getSplitName() {
return mSplitName;
}
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 71d3202..8c86db6 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -32,6 +32,7 @@
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.Permission;
import android.content.pm.PermissionInfo;
+import android.content.pm.Signature;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
@@ -129,6 +130,9 @@
public PackageSettingBase getSourcePackageSetting() {
return sourcePackageSetting;
}
+ public Signature[] getSourceSignatures() {
+ return sourcePackageSetting.getSignatures();
+ }
public int getType() {
return type;
}
@@ -277,8 +281,8 @@
// Allow system apps to redefine non-system permissions
if (bp != null && !Objects.equals(bp.sourcePackageName, p.info.packageName)) {
final boolean currentOwnerIsSystem = (bp.perm != null
- && bp.perm.owner.isSystemApp());
- if (p.owner.isSystemApp()) {
+ && bp.perm.owner.isSystem());
+ if (p.owner.isSystem()) {
if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
// It's a built-in permission and no owner, take ownership now
bp.sourcePackageSetting = pkgSetting;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 161efd3..8014acf 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -383,6 +383,7 @@
MediaStore.AUTHORITY, userId);
if (mediaStorePackage != null) {
grantRuntimePermissions(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
+ grantRuntimePermissions(mediaStorePackage, PHONE_PERMISSIONS, true, userId);
}
// Downloads provider
@@ -985,7 +986,7 @@
private PackageParser.Package getSystemPackage(String packageName) {
PackageParser.Package pkg = getPackage(packageName);
- if (pkg != null && pkg.isSystemApp()) {
+ if (pkg != null && pkg.isSystem()) {
return !isSysComponentOrPersistentPlatformSignedPrivApp(pkg) ? pkg : null;
}
return null;
@@ -1094,7 +1095,7 @@
if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
return true;
}
- if (!pkg.isPrivilegedApp()) {
+ if (!pkg.isPrivileged()) {
return false;
}
final PackageParser.Package disabledPkg =
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
new file mode 100644
index 0000000..6c8c9b2
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -0,0 +1,7 @@
+per-file DefaultPermissionGrantPolicy.java = bpoiesz@google.com
+per-file DefaultPermissionGrantPolicy.java = fkupolov@google.com
+per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
+per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
+per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com
+per-file DefaultPermissionGrantPolicy.java = toddke@google.com
+per-file DefaultPermissionGrantPolicy.java = yamasani@google.com
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java
index 8aac52a..60c118b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageParser;
+import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.PermissionInfoFlags;
@@ -57,7 +58,7 @@
}
public void onInstallPermissionRevoked() {
}
- public void onPermissionUpdated(int userId) {
+ public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
}
public void onPermissionRemoved() {
}
@@ -65,6 +66,10 @@
}
}
+ public abstract void systemReady();
+
+ public abstract boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId);
+
public abstract void grantRuntimePermission(
@NonNull String permName, @NonNull String packageName, boolean overridePolicy,
int callingUid, int userId, @Nullable PermissionCallback callback);
@@ -78,9 +83,12 @@
public abstract void revokeRuntimePermission(@NonNull String permName,
@NonNull String packageName, boolean overridePolicy, int callingUid, int userId,
@Nullable PermissionCallback callback);
- public abstract int[] revokeUnusedSharedUserPermissions(@NonNull SharedUserSetting suSetting,
- @NonNull int[] allUserIds);
+ public abstract void updatePermissions(@Nullable String packageName,
+ @Nullable PackageParser.Package pkg, boolean replaceGrant,
+ @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback);
+ public abstract void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdated,
+ @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback);
/**
* Add all permissions in the given package.
@@ -89,22 +97,28 @@
* the permission settings.
*/
public abstract void addAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
+ public abstract void addAllPermissionGroups(@NonNull PackageParser.Package pkg, boolean chatty);
public abstract void removeAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
public abstract boolean addDynamicPermission(@NonNull PermissionInfo info, boolean async,
int callingUid, @Nullable PermissionCallback callback);
public abstract void removeDynamicPermission(@NonNull String permName, int callingUid,
@Nullable PermissionCallback callback);
- public abstract int updatePermissions(@Nullable String changingPkg,
- @Nullable PackageParser.Package pkgInfo, int flags);
- public abstract int updatePermissionTrees(@Nullable String changingPkg,
- @Nullable PackageParser.Package pkgInfo, int flags);
-
public abstract @Nullable String[] getAppOpPermissionPackages(@NonNull String permName);
public abstract int getPermissionFlags(@NonNull String permName,
@NonNull String packageName, int callingUid, int userId);
/**
+ * Retrieve all of the information we know about a particular group of permissions.
+ */
+ public abstract @Nullable PermissionGroupInfo getPermissionGroupInfo(
+ @NonNull String groupName, int flags, int callingUid);
+ /**
+ * Retrieve all of the known permission groups in the system.
+ */
+ public abstract @Nullable List<PermissionGroupInfo> getAllPermissionGroups(int flags,
+ int callingUid);
+ /**
* Retrieve all of the information we know about a particular permission.
*/
public abstract @Nullable PermissionInfo getPermissionInfo(@NonNull String permName,
@@ -132,6 +146,7 @@
public abstract int checkPermission(@NonNull String permName, @NonNull String packageName,
int callingUid, int userId);
+ public abstract int checkUidPermission(String permName, int uid, int callingUid);
/**
* Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
@@ -147,8 +162,5 @@
public abstract @NonNull DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy();
/** HACK HACK methods to allow for partial migration of data to the PermissionManager class */
- public abstract Iterator<BasePermission> getPermissionIteratorTEMP();
public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);
- public abstract void putPermissionTEMP(@NonNull String permName,
- @NonNull BasePermission permission);
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d2d857c..76805ce 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -18,6 +18,13 @@
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
+import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
+import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS;
+import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import android.Manifest;
import android.annotation.NonNull;
@@ -27,7 +34,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
-import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.PackageParser.Package;
import android.os.Binder;
@@ -35,18 +42,24 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
+import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.RoSystemProperties;
import com.android.internal.util.ArrayUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -58,6 +71,7 @@
import com.android.server.pm.PackageSetting;
import com.android.server.pm.ProcessLoggingHandler;
import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.UserManagerService;
import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
import com.android.server.pm.permission.PermissionsState.PermissionState;
@@ -69,6 +83,7 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -107,8 +122,19 @@
Manifest.permission.READ_PHONE_NUMBERS,
Manifest.permission.ANSWER_PHONE_CALLS);
- /** Cap the size of permission trees that 3rd party apps can define */
- private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; // characters of text
+ /** Permission grant: not grant the permission. */
+ private static final int GRANT_DENIED = 1;
+ /** Permission grant: grant the permission as an install permission. */
+ private static final int GRANT_INSTALL = 2;
+ /** Permission grant: grant the permission as a runtime one. */
+ private static final int GRANT_RUNTIME = 3;
+ /** Permission grant: grant as runtime a permission that was granted as an install time one. */
+ private static final int GRANT_UPGRADE = 4;
+
+ /** Cap the size of permission trees that 3rd party apps can define; in characters of text */
+ private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;
+ /** Empty array to avoid allocations */
+ private static final int[] EMPTY_INT_ARRAY = new int[0];
/** Lock to protect internal data access */
private final Object mLock;
@@ -122,13 +148,29 @@
/** Default permission policy to provide proper behaviour out-of-the-box */
private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy;
- /** Internal storage for permissions and related settings */
- private final PermissionSettings mSettings;
+ /**
+ * Built-in permissions. Read from system configuration files. Mapping is from
+ * UID to permission name.
+ */
+ private final SparseArray<ArraySet<String>> mSystemPermissions;
+
+ /** Built-in group IDs given to all packages. Read from system configuration files. */
+ private final int[] mGlobalGids;
private final HandlerThread mHandlerThread;
private final Handler mHandler;
private final Context mContext;
+ /** Internal storage for permissions and related settings */
+ @GuardedBy("mLock")
+ private final PermissionSettings mSettings;
+
+ @GuardedBy("mLock")
+ private ArraySet<String> mPrivappPermissionsViolations;
+
+ @GuardedBy("mLock")
+ private boolean mSystemReady;
+
PermissionManagerService(Context context,
@Nullable DefaultPermissionGrantedCallback defaultGrantCallback,
@NonNull Object externalLock) {
@@ -146,6 +188,9 @@
mDefaultPermissionGrantPolicy = new DefaultPermissionGrantPolicy(
context, mHandlerThread.getLooper(), defaultGrantCallback, this);
+ SystemConfig systemConfig = SystemConfig.getInstance();
+ mSystemPermissions = systemConfig.getSystemPermissions();
+ mGlobalGids = systemConfig.getGlobalGids();
// propagate permission configuration
final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
@@ -230,14 +275,105 @@
return PackageManager.PERMISSION_DENIED;
}
- private PermissionInfo getPermissionInfo(String name, String packageName, int flags,
+ private int checkUidPermission(String permName, int uid, int callingUid) {
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ final boolean isCallerInstantApp =
+ mPackageManagerInt.getInstantAppPackageName(callingUid) != null;
+ final boolean isUidInstantApp =
+ mPackageManagerInt.getInstantAppPackageName(uid) != null;
+ final int userId = UserHandle.getUserId(uid);
+ if (!mUserManagerInt.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length > 0) {
+ PackageParser.Package pkg = null;
+ for (String packageName : packages) {
+ pkg = mPackageManagerInt.getPackage(packageName);
+ if (pkg != null) {
+ break;
+ }
+ }
+ if (pkg == null) {
+Slog.e(TAG, "TODD: No package not found; UID: " + uid);
+Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages));
+ return PackageManager.PERMISSION_DENIED;
+ }
+ if (pkg.mSharedUserId != null) {
+ if (isCallerInstantApp) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ } else {
+ if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ }
+ final PermissionsState permissionsState =
+ ((PackageSetting) pkg.mExtras).getPermissionsState();
+ if (permissionsState.hasPermission(permName, userId)) {
+ if (isUidInstantApp) {
+ if (mSettings.isPermissionInstant(permName)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ } else {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
+ if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
+ .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ } else {
+ ArraySet<String> perms = mSystemPermissions.get(uid);
+ if (perms != null) {
+ if (perms.contains(permName)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
+ .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ }
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ private PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags,
+ int callingUid) {
+ if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+ return null;
+ }
+ synchronized (mLock) {
+ return PackageParser.generatePermissionGroupInfo(
+ mSettings.mPermissionGroups.get(groupName), flags);
+ }
+ }
+
+ private List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) {
+ if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+ return null;
+ }
+ synchronized (mLock) {
+ final int N = mSettings.mPermissionGroups.size();
+ final ArrayList<PermissionGroupInfo> out
+ = new ArrayList<PermissionGroupInfo>(N);
+ for (PackageParser.PermissionGroup pg : mSettings.mPermissionGroups.values()) {
+ out.add(PackageParser.generatePermissionGroupInfo(pg, flags));
+ }
+ return out;
+ }
+ }
+
+ private PermissionInfo getPermissionInfo(String permName, String packageName, int flags,
int callingUid) {
if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
return null;
}
// reader
synchronized (mLock) {
- final BasePermission bp = mSettings.getPermissionLocked(name);
+ final BasePermission bp = mSettings.getPermissionLocked(permName);
if (bp == null) {
return null;
}
@@ -252,14 +388,10 @@
if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
return null;
}
- // reader
synchronized (mLock) {
- // TODO Uncomment when mPermissionGroups moves to this class
-// if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
-// // This is thrown as NameNotFoundException
-// return null;
-// }
-
+ if (groupName != null && !mSettings.mPermissionGroups.containsKey(groupName)) {
+ return null;
+ }
final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
for (BasePermission bp : mSettings.mPermissions.values()) {
final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags);
@@ -314,21 +446,21 @@
// Assume by default that we did not install this permission into the system.
p.info.flags &= ~PermissionInfo.FLAG_INSTALLED;
- // Now that permission groups have a special meaning, we ignore permission
- // groups for legacy apps to prevent unexpected behavior. In particular,
- // permissions for one app being granted to someone just because they happen
- // to be in a group defined by another app (before this had no implications).
- if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
- p.group = mPackageManagerInt.getPermissionGroupTEMP(p.info.group);
- // Warn for a permission in an unknown group.
- if (PackageManagerService.DEBUG_PERMISSIONS
- && p.info.group != null && p.group == null) {
- Slog.i(TAG, "Permission " + p.info.name + " from package "
- + p.info.packageName + " in an unknown group " + p.info.group);
- }
- }
-
synchronized (PermissionManagerService.this.mLock) {
+ // Now that permission groups have a special meaning, we ignore permission
+ // groups for legacy apps to prevent unexpected behavior. In particular,
+ // permissions for one app being granted to someone just because they happen
+ // to be in a group defined by another app (before this had no implications).
+ if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+ p.group = mSettings.mPermissionGroups.get(p.info.group);
+ // Warn for a permission in an unknown group.
+ if (DEBUG_PERMISSIONS
+ && p.info.group != null && p.group == null) {
+ Slog.i(TAG, "Permission " + p.info.name + " from package "
+ + p.info.packageName + " in an unknown group " + p.info.group);
+ }
+ }
+
if (p.tree) {
final BasePermission bp = BasePermission.createOrUpdate(
mSettings.getPermissionTreeLocked(p.info.name), p, pkg,
@@ -344,6 +476,48 @@
}
}
+ private void addAllPermissionGroups(PackageParser.Package pkg, boolean chatty) {
+ final int N = pkg.permissionGroups.size();
+ StringBuilder r = null;
+ for (int i=0; i<N; i++) {
+ final PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
+ final PackageParser.PermissionGroup cur = mSettings.mPermissionGroups.get(pg.info.name);
+ final String curPackageName = (cur == null) ? null : cur.info.packageName;
+ final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
+ if (cur == null || isPackageUpdate) {
+ mSettings.mPermissionGroups.put(pg.info.name, pg);
+ if (chatty && DEBUG_PACKAGE_SCANNING) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ if (isPackageUpdate) {
+ r.append("UPD:");
+ }
+ r.append(pg.info.name);
+ }
+ } else {
+ Slog.w(TAG, "Permission group " + pg.info.name + " from package "
+ + pg.info.packageName + " ignored: original from "
+ + cur.info.packageName);
+ if (chatty && DEBUG_PACKAGE_SCANNING) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ r.append("DUP:");
+ r.append(pg.info.name);
+ }
+ }
+ }
+ if (r != null && DEBUG_PACKAGE_SCANNING) {
+ Log.d(TAG, " Permission Groups: " + r);
+ }
+
+ }
+
private void removeAllPermissions(PackageParser.Package pkg, boolean chatty) {
synchronized (mLock) {
int N = pkg.permissions.size();
@@ -356,7 +530,7 @@
}
if (bp != null && bp.isPermission(p)) {
bp.setPermission(null);
- if (PackageManagerService.DEBUG_REMOVE && chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -374,7 +548,7 @@
}
}
if (r != null) {
- if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r);
+ if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r);
}
N = pkg.requestedPermissions.size();
@@ -392,7 +566,7 @@
}
}
if (r != null) {
- if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r);
+ if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r);
}
}
}
@@ -455,6 +629,581 @@
}
}
+ private void grantPermissions(PackageParser.Package pkg, boolean replace,
+ String packageOfInterest, PermissionCallback callback) {
+ // IMPORTANT: There are two types of permissions: install and runtime.
+ // Install time permissions are granted when the app is installed to
+ // all device users and users added in the future. Runtime permissions
+ // are granted at runtime explicitly to specific users. Normal and signature
+ // protected permissions are install time permissions. Dangerous permissions
+ // are install permissions if the app's target SDK is Lollipop MR1 or older,
+ // otherwise they are runtime permissions. This function does not manage
+ // runtime permissions except for the case an app targeting Lollipop MR1
+ // being upgraded to target a newer SDK, in which case dangerous permissions
+ // are transformed from install time to runtime ones.
+
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null) {
+ return;
+ }
+ final boolean isLegacySystemApp = mPackageManagerInt.isLegacySystemApp(pkg);
+
+ final PermissionsState permissionsState = ps.getPermissionsState();
+ PermissionsState origPermissions = permissionsState;
+
+ final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
+
+ boolean runtimePermissionsRevoked = false;
+ int[] updatedUserIds = EMPTY_INT_ARRAY;
+
+ boolean changedInstallPermission = false;
+
+ if (replace) {
+ ps.setInstallPermissionsFixed(false);
+ if (!ps.isSharedUser()) {
+ origPermissions = new PermissionsState(permissionsState);
+ permissionsState.reset();
+ } else {
+ // We need to know only about runtime permission changes since the
+ // calling code always writes the install permissions state but
+ // the runtime ones are written only if changed. The only cases of
+ // changed runtime permissions here are promotion of an install to
+ // runtime and revocation of a runtime from a shared user.
+ synchronized (mLock) {
+ updatedUserIds = revokeUnusedSharedUserPermissionsLocked(
+ ps.getSharedUser(), UserManagerService.getInstance().getUserIds());
+ if (!ArrayUtils.isEmpty(updatedUserIds)) {
+ runtimePermissionsRevoked = true;
+ }
+ }
+ }
+ }
+
+ permissionsState.setGlobalGids(mGlobalGids);
+
+ synchronized (mLock) {
+ final int N = pkg.requestedPermissions.size();
+ for (int i = 0; i < N; i++) {
+ final String permName = pkg.requestedPermissions.get(i);
+ final BasePermission bp = mSettings.getPermissionLocked(permName);
+ final boolean appSupportsRuntimePermissions =
+ pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
+
+ if (DEBUG_INSTALL) {
+ Log.i(TAG, "Package " + pkg.packageName + " checking " + permName + ": " + bp);
+ }
+
+ if (bp == null || bp.getSourcePackageSetting() == null) {
+ if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
+ if (DEBUG_PERMISSIONS) {
+ Slog.i(TAG, "Unknown permission " + permName
+ + " in package " + pkg.packageName);
+ }
+ }
+ continue;
+ }
+
+ // Limit ephemeral apps to ephemeral allowed permissions.
+ if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
+ if (DEBUG_PERMISSIONS) {
+ Log.i(TAG, "Denying non-ephemeral permission " + bp.getName()
+ + " for package " + pkg.packageName);
+ }
+ continue;
+ }
+
+ if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
+ if (DEBUG_PERMISSIONS) {
+ Log.i(TAG, "Denying runtime-only permission " + bp.getName()
+ + " for package " + pkg.packageName);
+ }
+ continue;
+ }
+
+ final String perm = bp.getName();
+ boolean allowedSig = false;
+ int grant = GRANT_DENIED;
+
+ // Keep track of app op permissions.
+ if (bp.isAppOp()) {
+ mSettings.addAppOpPackage(perm, pkg.packageName);
+ }
+
+ if (bp.isNormal()) {
+ // For all apps normal permissions are install time ones.
+ grant = GRANT_INSTALL;
+ } else if (bp.isRuntime()) {
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted runtime ones since we need
+ // to keep the review required permission flag per user while an
+ // install permission's state is shared across all users.
+ if (!appSupportsRuntimePermissions && !mSettings.mPermissionReviewRequired) {
+ // For legacy apps dangerous permissions are install time ones.
+ grant = GRANT_INSTALL;
+ } else if (origPermissions.hasInstallPermission(bp.getName())) {
+ // For legacy apps that became modern, install becomes runtime.
+ grant = GRANT_UPGRADE;
+ } else if (isLegacySystemApp) {
+ // For legacy system apps, install becomes runtime.
+ // We cannot check hasInstallPermission() for system apps since those
+ // permissions were granted implicitly and not persisted pre-M.
+ grant = GRANT_UPGRADE;
+ } else {
+ // For modern apps keep runtime permissions unchanged.
+ grant = GRANT_RUNTIME;
+ }
+ } else if (bp.isSignature()) {
+ // For all apps signature permissions are install time ones.
+ allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
+ if (allowedSig) {
+ grant = GRANT_INSTALL;
+ }
+ }
+
+ if (DEBUG_PERMISSIONS) {
+ Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName);
+ }
+
+ if (grant != GRANT_DENIED) {
+ if (!ps.isSystem() && ps.areInstallPermissionsFixed()) {
+ // If this is an existing, non-system package, then
+ // we can't add any new permissions to it.
+ if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
+ // Except... if this is a permission that was added
+ // to the platform (note: need to only do this when
+ // updating the platform).
+ if (!isNewPlatformPermissionForPackage(perm, pkg)) {
+ grant = GRANT_DENIED;
+ }
+ }
+ }
+
+ switch (grant) {
+ case GRANT_INSTALL: {
+ // Revoke this as runtime permission to handle the case of
+ // a runtime permission being downgraded to an install one.
+ // Also in permission review mode we keep dangerous permissions
+ // for legacy apps
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ if (origPermissions.getRuntimePermissionState(
+ perm, userId) != null) {
+ // Revoke the runtime permission and clear the flags.
+ origPermissions.revokeRuntimePermission(bp, userId);
+ origPermissions.updatePermissionFlags(bp, userId,
+ PackageManager.MASK_PERMISSION_FLAGS, 0);
+ // If we revoked a permission permission, we have to write.
+ updatedUserIds = ArrayUtils.appendInt(
+ updatedUserIds, userId);
+ }
+ }
+ // Grant an install permission.
+ if (permissionsState.grantInstallPermission(bp) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ changedInstallPermission = true;
+ }
+ } break;
+
+ case GRANT_RUNTIME: {
+ // Grant previously granted runtime permissions.
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ final PermissionState permissionState = origPermissions
+ .getRuntimePermissionState(perm, userId);
+ int flags = permissionState != null
+ ? permissionState.getFlags() : 0;
+ if (origPermissions.hasRuntimePermission(perm, userId)) {
+ // Don't propagate the permission in a permission review
+ // mode if the former was revoked, i.e. marked to not
+ // propagate on upgrade. Note that in a permission review
+ // mode install permissions are represented as constantly
+ // granted runtime ones since we need to keep a per user
+ // state associated with the permission. Also the revoke
+ // on upgrade flag is no longer applicable and is reset.
+ final boolean revokeOnUpgrade = (flags & PackageManager
+ .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
+ if (revokeOnUpgrade) {
+ flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+ // Since we changed the flags, we have to write.
+ updatedUserIds = ArrayUtils.appendInt(
+ updatedUserIds, userId);
+ }
+ if (!mSettings.mPermissionReviewRequired || !revokeOnUpgrade) {
+ if (permissionsState.grantRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ // If we cannot put the permission as it was,
+ // we have to write.
+ updatedUserIds = ArrayUtils.appendInt(
+ updatedUserIds, userId);
+ }
+ }
+
+ // If the app supports runtime permissions no need for a review.
+ if (mSettings.mPermissionReviewRequired
+ && appSupportsRuntimePermissions
+ && (flags & PackageManager
+ .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+ // Since we changed the flags, we have to write.
+ updatedUserIds = ArrayUtils.appendInt(
+ updatedUserIds, userId);
+ }
+ } else if (mSettings.mPermissionReviewRequired
+ && !appSupportsRuntimePermissions) {
+ // For legacy apps that need a permission review, every new
+ // runtime permission is granted but it is pending a review.
+ // We also need to review only platform defined runtime
+ // permissions as these are the only ones the platform knows
+ // how to disable the API to simulate revocation as legacy
+ // apps don't expect to run with revoked permissions.
+ if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) {
+ if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+ flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
+ // We changed the flags, hence have to write.
+ updatedUserIds = ArrayUtils.appendInt(
+ updatedUserIds, userId);
+ }
+ }
+ if (permissionsState.grantRuntimePermission(bp, userId)
+ != PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ // We changed the permission, hence have to write.
+ updatedUserIds = ArrayUtils.appendInt(
+ updatedUserIds, userId);
+ }
+ }
+ // Propagate the permission flags.
+ permissionsState.updatePermissionFlags(bp, userId, flags, flags);
+ }
+ } break;
+
+ case GRANT_UPGRADE: {
+ // Grant runtime permissions for a previously held install permission.
+ final PermissionState permissionState = origPermissions
+ .getInstallPermissionState(perm);
+ final int flags =
+ (permissionState != null) ? permissionState.getFlags() : 0;
+
+ if (origPermissions.revokeInstallPermission(bp)
+ != PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ // We will be transferring the permission flags, so clear them.
+ origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL,
+ PackageManager.MASK_PERMISSION_FLAGS, 0);
+ changedInstallPermission = true;
+ }
+
+ // If the permission is not to be promoted to runtime we ignore it and
+ // also its other flags as they are not applicable to install permissions.
+ if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) {
+ for (int userId : currentUserIds) {
+ if (permissionsState.grantRuntimePermission(bp, userId) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ // Transfer the permission flags.
+ permissionsState.updatePermissionFlags(bp, userId,
+ flags, flags);
+ // If we granted the permission, we have to write.
+ updatedUserIds = ArrayUtils.appendInt(
+ updatedUserIds, userId);
+ }
+ }
+ }
+ } break;
+
+ default: {
+ if (packageOfInterest == null
+ || packageOfInterest.equals(pkg.packageName)) {
+ if (DEBUG_PERMISSIONS) {
+ Slog.i(TAG, "Not granting permission " + perm
+ + " to package " + pkg.packageName
+ + " because it was previously installed without");
+ }
+ }
+ } break;
+ }
+ } else {
+ if (permissionsState.revokeInstallPermission(bp) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ // Also drop the permission flags.
+ permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+ PackageManager.MASK_PERMISSION_FLAGS, 0);
+ changedInstallPermission = true;
+ Slog.i(TAG, "Un-granting permission " + perm
+ + " from package " + pkg.packageName
+ + " (protectionLevel=" + bp.getProtectionLevel()
+ + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+ + ")");
+ } else if (bp.isAppOp()) {
+ // Don't print warning for app op permissions, since it is fine for them
+ // not to be granted, there is a UI for the user to decide.
+ if (DEBUG_PERMISSIONS
+ && (packageOfInterest == null
+ || packageOfInterest.equals(pkg.packageName))) {
+ Slog.i(TAG, "Not granting permission " + perm
+ + " to package " + pkg.packageName
+ + " (protectionLevel=" + bp.getProtectionLevel()
+ + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+ + ")");
+ }
+ }
+ }
+ }
+
+ if ((changedInstallPermission || replace) && !ps.areInstallPermissionsFixed() &&
+ !ps.isSystem() || ps.isUpdatedSystem()) {
+ // This is the first that we have heard about this package, so the
+ // permissions we have now selected are fixed until explicitly
+ // changed.
+ ps.setInstallPermissionsFixed(true);
+ }
+ }
+
+ // Persist the runtime permissions state for users with changes. If permissions
+ // were revoked because no app in the shared user declares them we have to
+ // write synchronously to avoid losing runtime permissions state.
+ if (callback != null) {
+ callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked);
+ }
+ }
+
+ private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
+ boolean allowed = false;
+ final int NP = PackageParser.NEW_PERMISSIONS.length;
+ for (int ip=0; ip<NP; ip++) {
+ final PackageParser.NewPermissionInfo npi
+ = PackageParser.NEW_PERMISSIONS[ip];
+ if (npi.name.equals(perm)
+ && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
+ allowed = true;
+ Log.i(TAG, "Auto-granting " + perm + " to old pkg "
+ + pkg.packageName);
+ break;
+ }
+ }
+ return allowed;
+ }
+
+ /**
+ * Determines whether a package is whitelisted for a particular privapp permission.
+ *
+ * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted.
+ *
+ * <p>This handles parent/child apps.
+ */
+ private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) {
+ ArraySet<String> wlPermissions = SystemConfig.getInstance()
+ .getPrivAppPermissions(pkg.packageName);
+ // Let's check if this package is whitelisted...
+ boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm);
+ // If it's not, we'll also tail-recurse to the parent.
+ return whitelisted ||
+ pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage);
+ }
+
+ private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
+ BasePermission bp, PermissionsState origPermissions) {
+ boolean oemPermission = bp.isOEM();
+ boolean privilegedPermission = bp.isPrivileged();
+ boolean privappPermissionsDisable =
+ RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
+ boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
+ boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
+ if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivileged()
+ && !platformPackage && platformPermission) {
+ if (!hasPrivappWhitelistEntry(perm, pkg)) {
+ // Only report violations for apps on system image
+ if (!mSystemReady && !pkg.isUpdatedSystemApp()) {
+ // it's only a reportable violation if the permission isn't explicitly denied
+ final ArraySet<String> deniedPermissions = SystemConfig.getInstance()
+ .getPrivAppDenyPermissions(pkg.packageName);
+ final boolean permissionViolation =
+ deniedPermissions == null || !deniedPermissions.contains(perm);
+ if (permissionViolation) {
+ Slog.w(TAG, "Privileged permission " + perm + " for package "
+ + pkg.packageName + " - not in privapp-permissions whitelist");
+
+ if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+ if (mPrivappPermissionsViolations == null) {
+ mPrivappPermissionsViolations = new ArraySet<>();
+ }
+ mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm);
+ }
+ } else {
+ return false;
+ }
+ }
+ if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+ return false;
+ }
+ }
+ }
+ final String systemPackageName = mPackageManagerInt.getKnownPackageName(
+ PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
+ final PackageParser.Package systemPackage =
+ mPackageManagerInt.getPackage(systemPackageName);
+ boolean allowed = (PackageManagerService.compareSignatures(
+ bp.getSourceSignatures(), pkg.mSignatures)
+ == PackageManager.SIGNATURE_MATCH)
+ || (PackageManagerService.compareSignatures(
+ systemPackage.mSignatures, pkg.mSignatures)
+ == PackageManager.SIGNATURE_MATCH);
+ if (!allowed && (privilegedPermission || oemPermission)) {
+ if (pkg.isSystem()) {
+ // For updated system applications, a privileged/oem permission
+ // is granted only if it had been defined by the original application.
+ if (pkg.isUpdatedSystemApp()) {
+ final PackageParser.Package disabledPkg =
+ mPackageManagerInt.getDisabledPackage(pkg.packageName);
+ final PackageSetting disabledPs =
+ (disabledPkg != null) ? (PackageSetting) disabledPkg.mExtras : null;
+ if (disabledPs != null
+ && disabledPs.getPermissionsState().hasInstallPermission(perm)) {
+ // If the original was granted this permission, we take
+ // that grant decision as read and propagate it to the
+ // update.
+ if ((privilegedPermission && disabledPs.isPrivileged())
+ || (oemPermission && disabledPs.isOem()
+ && canGrantOemPermission(disabledPs, perm))) {
+ allowed = true;
+ }
+ } else {
+ // The system apk may have been updated with an older
+ // version of the one on the data partition, but which
+ // granted a new system permission that it didn't have
+ // before. In this case we do want to allow the app to
+ // now get the new permission if the ancestral apk is
+ // privileged to get it.
+ if (disabledPs != null && disabledPkg != null
+ && isPackageRequestingPermission(disabledPkg, perm)
+ && ((privilegedPermission && disabledPs.isPrivileged())
+ || (oemPermission && disabledPs.isOem()
+ && canGrantOemPermission(disabledPs, perm)))) {
+ allowed = true;
+ }
+ // Also if a privileged parent package on the system image or any of
+ // its children requested a privileged/oem permission, the updated child
+ // packages can also get the permission.
+ if (pkg.parentPackage != null) {
+ final PackageParser.Package disabledParentPkg = mPackageManagerInt
+ .getDisabledPackage(pkg.parentPackage.packageName);
+ final PackageSetting disabledParentPs = (disabledParentPkg != null)
+ ? (PackageSetting) disabledParentPkg.mExtras : null;
+ if (disabledParentPkg != null
+ && ((privilegedPermission && disabledParentPs.isPrivileged())
+ || (oemPermission && disabledParentPs.isOem()))) {
+ if (isPackageRequestingPermission(disabledParentPkg, perm)
+ && canGrantOemPermission(disabledParentPs, perm)) {
+ allowed = true;
+ } else if (disabledParentPkg.childPackages != null) {
+ for (PackageParser.Package disabledChildPkg
+ : disabledParentPkg.childPackages) {
+ final PackageSetting disabledChildPs =
+ (disabledChildPkg != null)
+ ? (PackageSetting) disabledChildPkg.mExtras
+ : null;
+ if (isPackageRequestingPermission(disabledChildPkg, perm)
+ && canGrantOemPermission(
+ disabledChildPs, perm)) {
+ allowed = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ allowed = (privilegedPermission && pkg.isPrivileged())
+ || (oemPermission && pkg.isOem()
+ && canGrantOemPermission(ps, perm));
+ }
+ }
+ }
+ if (!allowed) {
+ if (!allowed
+ && bp.isPre23()
+ && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+ // If this was a previously normal/dangerous permission that got moved
+ // to a system permission as part of the runtime permission redesign, then
+ // we still want to blindly grant it to old apps.
+ allowed = true;
+ }
+ if (!allowed && bp.isInstaller()
+ && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+ PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM))) {
+ // If this permission is to be granted to the system installer and
+ // this app is an installer, then it gets the permission.
+ allowed = true;
+ }
+ if (!allowed && bp.isVerifier()
+ && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+ PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM))) {
+ // If this permission is to be granted to the system verifier and
+ // this app is a verifier, then it gets the permission.
+ allowed = true;
+ }
+ if (!allowed && bp.isPreInstalled()
+ && pkg.isSystem()) {
+ // Any pre-installed system app is allowed to get this permission.
+ allowed = true;
+ }
+ if (!allowed && bp.isDevelopment()) {
+ // For development permissions, a development permission
+ // is granted only if it was already granted.
+ allowed = origPermissions.hasInstallPermission(perm);
+ }
+ if (!allowed && bp.isSetup()
+ && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+ PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM))) {
+ // If this permission is to be granted to the system setup wizard and
+ // this app is a setup wizard, then it gets the permission.
+ allowed = true;
+ }
+ }
+ return allowed;
+ }
+
+ private static boolean canGrantOemPermission(PackageSetting ps, String permission) {
+ if (!ps.isOem()) {
+ return false;
+ }
+ // all oem permissions must explicitly be granted or denied
+ final Boolean granted =
+ SystemConfig.getInstance().getOemPermissions(ps.name).get(permission);
+ if (granted == null) {
+ throw new IllegalStateException("OEM permission" + permission + " requested by package "
+ + ps.name + " must be explicitly declared granted or not");
+ }
+ return Boolean.TRUE == granted;
+ }
+
+ private boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId) {
+ if (!mSettings.mPermissionReviewRequired) {
+ return false;
+ }
+
+ // Permission review applies only to apps not supporting the new permission model.
+ if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+ return false;
+ }
+
+ // Legacy apps have the permission and get user consent on launch.
+ if (pkg == null || pkg.mExtras == null) {
+ return false;
+ }
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ final PermissionsState permissionsState = ps.getPermissionsState();
+ return permissionsState.isPermissionReviewRequired(userId);
+ }
+
+ private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) {
+ final int permCount = pkg.requestedPermissions.size();
+ for (int j = 0; j < permCount; j++) {
+ String requestedPermission = pkg.requestedPermissions.get(j);
+ if (permission.equals(requestedPermission)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void grantRuntimePermissionsGrantedToDisabledPackageLocked(
PackageParser.Package pkg, int callingUid, PermissionCallback callback) {
if (pkg.parentPackage == null) {
@@ -744,7 +1493,8 @@
}
}
- private int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting, int[] allUserIds) {
+ private int[] revokeUnusedSharedUserPermissionsLocked(
+ SharedUserSetting suSetting, int[] allUserIds) {
// Collect all used permissions in the UID
final ArraySet<String> usedPermissions = new ArraySet<>();
final List<PackageParser.Package> pkgList = suSetting.getPackages();
@@ -845,7 +1595,79 @@
return permissionsState.getPermissionFlags(permName, userId);
}
- private int updatePermissions(String packageName, PackageParser.Package pkgInfo, int flags) {
+ private static final int UPDATE_PERMISSIONS_ALL = 1<<0;
+ private static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
+ private static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
+
+ private void updatePermissions(String packageName, PackageParser.Package pkg,
+ boolean replaceGrant, Collection<PackageParser.Package> allPackages,
+ PermissionCallback callback) {
+ final int flags = (pkg != null ? UPDATE_PERMISSIONS_ALL : 0) |
+ (replaceGrant ? UPDATE_PERMISSIONS_REPLACE_PKG : 0);
+ updatePermissions(
+ packageName, pkg, getVolumeUuidForPackage(pkg), flags, allPackages, callback);
+ if (pkg != null && pkg.childPackages != null) {
+ for (PackageParser.Package childPkg : pkg.childPackages) {
+ updatePermissions(childPkg.packageName, childPkg,
+ getVolumeUuidForPackage(childPkg), flags, allPackages, callback);
+ }
+ }
+ }
+
+ private void updateAllPermissions(String volumeUuid, boolean sdkUpdated,
+ Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+ final int flags = UPDATE_PERMISSIONS_ALL |
+ (sdkUpdated
+ ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL
+ : 0);
+ updatePermissions(null, null, volumeUuid, flags, allPackages, callback);
+ }
+
+ private void updatePermissions(String changingPkgName, PackageParser.Package changingPkg,
+ String replaceVolumeUuid, int flags, Collection<PackageParser.Package> allPackages,
+ PermissionCallback callback) {
+ // TODO: Most of the methods exposing BasePermission internals [source package name,
+ // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
+ // have package settings, we should make note of it elsewhere [map between
+ // source package name and BasePermission] and cycle through that here. Then we
+ // define a single method on BasePermission that takes a PackageSetting, changing
+ // package name and a package.
+ // NOTE: With this approach, we also don't need to tree trees differently than
+ // normal permissions. Today, we need two separate loops because these BasePermission
+ // objects are stored separately.
+ // Make sure there are no dangling permission trees.
+ flags = updatePermissionTrees(changingPkgName, changingPkg, flags);
+
+ // Make sure all dynamic permissions have been assigned to a package,
+ // and make sure there are no dangling permissions.
+ flags = updatePermissions(changingPkgName, changingPkg, flags);
+
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions");
+ // Now update the permissions for all packages, in particular
+ // replace the granted permissions of the system packages.
+ if ((flags & UPDATE_PERMISSIONS_ALL) != 0) {
+ for (PackageParser.Package pkg : allPackages) {
+ if (pkg != changingPkg) {
+ // Only replace for packages on requested volume
+ final String volumeUuid = getVolumeUuidForPackage(pkg);
+ final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
+ && Objects.equals(replaceVolumeUuid, volumeUuid);
+ grantPermissions(pkg, replace, changingPkgName, callback);
+ }
+ }
+ }
+
+ if (changingPkg != null) {
+ // Only replace for packages on requested volume
+ final String volumeUuid = getVolumeUuidForPackage(changingPkg);
+ final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
+ && Objects.equals(replaceVolumeUuid, volumeUuid);
+ grantPermissions(changingPkg, replace, changingPkgName, callback);
+ }
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+
+ private int updatePermissions(String packageName, PackageParser.Package pkg, int flags) {
Set<BasePermission> needsUpdate = null;
synchronized (mLock) {
final Iterator<BasePermission> it = mSettings.mPermissions.values().iterator();
@@ -856,10 +1678,10 @@
}
if (bp.getSourcePackageSetting() != null) {
if (packageName != null && packageName.equals(bp.getSourcePackageName())
- && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) {
+ && (pkg == null || !hasPermission(pkg, bp.getName()))) {
Slog.i(TAG, "Removing old permission tree: " + bp.getName()
+ " from package " + bp.getSourcePackageName());
- flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL;
+ flags |= UPDATE_PERMISSIONS_ALL;
it.remove();
}
continue;
@@ -872,13 +1694,13 @@
}
if (needsUpdate != null) {
for (final BasePermission bp : needsUpdate) {
- final PackageParser.Package pkg =
+ final PackageParser.Package sourcePkg =
mPackageManagerInt.getPackage(bp.getSourcePackageName());
synchronized (mLock) {
- if (pkg != null && pkg.mExtras != null) {
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (sourcePkg != null && sourcePkg.mExtras != null) {
+ final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras;
if (bp.getSourcePackageSetting() == null) {
- bp.setSourcePackageSetting(ps);
+ bp.setSourcePackageSetting(sourcePs);
}
continue;
}
@@ -891,7 +1713,7 @@
return flags;
}
- private int updatePermissionTrees(String packageName, PackageParser.Package pkgInfo,
+ private int updatePermissionTrees(String packageName, PackageParser.Package pkg,
int flags) {
Set<BasePermission> needsUpdate = null;
synchronized (mLock) {
@@ -900,10 +1722,10 @@
final BasePermission bp = it.next();
if (bp.getSourcePackageSetting() != null) {
if (packageName != null && packageName.equals(bp.getSourcePackageName())
- && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) {
+ && (pkg == null || !hasPermission(pkg, bp.getName()))) {
Slog.i(TAG, "Removing old permission tree: " + bp.getName()
+ " from package " + bp.getSourcePackageName());
- flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL;
+ flags |= UPDATE_PERMISSIONS_ALL;
it.remove();
}
continue;
@@ -916,13 +1738,13 @@
}
if (needsUpdate != null) {
for (final BasePermission bp : needsUpdate) {
- final PackageParser.Package pkg =
+ final PackageParser.Package sourcePkg =
mPackageManagerInt.getPackage(bp.getSourcePackageName());
synchronized (mLock) {
- if (pkg != null && pkg.mExtras != null) {
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (sourcePkg != null && sourcePkg.mExtras != null) {
+ final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras;
if (bp.getSourcePackageSetting() == null) {
- bp.setSourcePackageSetting(ps);
+ bp.setSourcePackageSetting(sourcePs);
}
continue;
}
@@ -985,7 +1807,7 @@
callback.onInstallPermissionUpdated();
} else if (permissionsState.getRuntimePermissionState(permName, userId) != null
|| hadState) {
- callback.onPermissionUpdated(userId);
+ callback.onPermissionUpdated(new int[] { userId }, false);
}
}
}
@@ -1083,6 +1905,29 @@
}
}
+ private void systemReady() {
+ mSystemReady = true;
+ if (mPrivappPermissionsViolations != null) {
+ throw new IllegalStateException("Signature|privileged permissions not in "
+ + "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
+ }
+ }
+
+ private static String getVolumeUuidForPackage(PackageParser.Package pkg) {
+ if (pkg == null) {
+ return StorageManager.UUID_PRIVATE_INTERNAL;
+ }
+ if (pkg.isExternal()) {
+ if (TextUtils.isEmpty(pkg.volumeUuid)) {
+ return StorageManager.UUID_PRIMARY_PHYSICAL;
+ } else {
+ return pkg.volumeUuid;
+ }
+ } else {
+ return StorageManager.UUID_PRIVATE_INTERNAL;
+ }
+ }
+
private static boolean hasPermission(PackageParser.Package pkgInfo, String permName) {
for (int i=pkgInfo.permissions.size()-1; i>=0; i--) {
if (pkgInfo.permissions.get(i).info.name.equals(permName)) {
@@ -1154,10 +1999,22 @@
private class PermissionManagerInternalImpl extends PermissionManagerInternal {
@Override
+ public void systemReady() {
+ PermissionManagerService.this.systemReady();
+ }
+ @Override
+ public boolean isPermissionsReviewRequired(Package pkg, int userId) {
+ return PermissionManagerService.this.isPermissionsReviewRequired(pkg, userId);
+ }
+ @Override
public void addAllPermissions(Package pkg, boolean chatty) {
PermissionManagerService.this.addAllPermissions(pkg, chatty);
}
@Override
+ public void addAllPermissionGroups(Package pkg, boolean chatty) {
+ PermissionManagerService.this.addAllPermissionGroups(pkg, chatty);
+ }
+ @Override
public void removeAllPermissions(Package pkg, boolean chatty) {
PermissionManagerService.this.removeAllPermissions(pkg, chatty);
}
@@ -1198,10 +2055,16 @@
overridePolicy, callingUid, userId, callback);
}
@Override
- public int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting,
- int[] allUserIds) {
- return PermissionManagerService.this.revokeUnusedSharedUserPermissions(
- (SharedUserSetting) suSetting, allUserIds);
+ public void updatePermissions(String packageName, Package pkg, boolean replaceGrant,
+ Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+ PermissionManagerService.this.updatePermissions(
+ packageName, pkg, replaceGrant, allPackages, callback);
+ }
+ @Override
+ public void updateAllPermissions(String volumeUuid, boolean sdkUpdated,
+ Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+ PermissionManagerService.this.updateAllPermissions(
+ volumeUuid, sdkUpdated, allPackages, callback);
}
@Override
public String[] getAppOpPermissionPackages(String permName) {
@@ -1214,16 +2077,6 @@
callingUid, userId);
}
@Override
- public int updatePermissions(String packageName,
- PackageParser.Package pkgInfo, int flags) {
- return PermissionManagerService.this.updatePermissions(packageName, pkgInfo, flags);
- }
- @Override
- public int updatePermissionTrees(String packageName,
- PackageParser.Package pkgInfo, int flags) {
- return PermissionManagerService.this.updatePermissionTrees(packageName, pkgInfo, flags);
- }
- @Override
public void updatePermissionFlags(String permName, String packageName, int flagMask,
int flagValues, int callingUid, int userId, PermissionCallback callback) {
PermissionManagerService.this.updatePermissionFlags(
@@ -1252,6 +2105,20 @@
permName, packageName, callingUid, userId);
}
@Override
+ public int checkUidPermission(String permName, int uid, int callingUid) {
+ return PermissionManagerService.this.checkUidPermission(permName, uid, callingUid);
+ }
+ @Override
+ public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags,
+ int callingUid) {
+ return PermissionManagerService.this.getPermissionGroupInfo(
+ groupName, flags, callingUid);
+ }
+ @Override
+ public List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) {
+ return PermissionManagerService.this.getAllPermissionGroups(flags, callingUid);
+ }
+ @Override
public PermissionInfo getPermissionInfo(String permName, String packageName, int flags,
int callingUid) {
return PermissionManagerService.this.getPermissionInfo(
@@ -1276,17 +2143,5 @@
return mSettings.getPermissionLocked(permName);
}
}
- @Override
- public void putPermissionTEMP(String permName, BasePermission permission) {
- synchronized (PermissionManagerService.this.mLock) {
- mSettings.putPermissionLocked(permName, (BasePermission) permission);
- }
- }
- @Override
- public Iterator<BasePermission> getPermissionIteratorTEMP() {
- synchronized (PermissionManagerService.this.mLock) {
- return mSettings.getAllPermissionsLocked().iterator();
- }
- }
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionSettings.java b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
index 7d125c9..f6c4990 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionSettings.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageParser;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -46,7 +47,8 @@
*/
public class PermissionSettings {
- final boolean mPermissionReviewRequired;
+ public final boolean mPermissionReviewRequired;
+
/**
* All of the permissions known to the system. The mapping is from permission
* name to permission object.
@@ -64,6 +66,14 @@
new ArrayMap<String, BasePermission>();
/**
+ * All permisson groups know to the system. The mapping is from permission group
+ * name to permission group object.
+ */
+ @GuardedBy("mLock")
+ final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups =
+ new ArrayMap<String, PackageParser.PermissionGroup>();
+
+ /**
* Set of packages that request a particular app op. The mapping is from permission
* name to package names.
*/
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index b917dae..2494bde 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -58,10 +58,6 @@
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.service.dreams.DreamManagerInternal;
-import android.service.power.PowerServiceDumpProto;
-import android.service.power.PowerServiceSettingsAndConfigurationDumpProto;
-import android.service.power.SuspendBlockerProto;
-import android.service.power.WakeLockProto;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.util.EventLog;
@@ -620,8 +616,8 @@
}
void dumpProto(ProtoOutputStream proto) {
- final long constantsToken = proto.start(PowerServiceDumpProto.CONSTANTS);
- proto.write(PowerServiceDumpProto.ConstantsProto.IS_NO_CACHED_WAKE_LOCKS,
+ final long constantsToken = proto.start(PowerManagerServiceDumpProto.CONSTANTS);
+ proto.write(PowerManagerServiceDumpProto.ConstantsProto.IS_NO_CACHED_WAKE_LOCKS,
NO_CACHED_WAKE_LOCKS);
proto.end(constantsToken);
}
@@ -3396,112 +3392,112 @@
synchronized (mLock) {
mConstants.dumpProto(proto);
- proto.write(PowerServiceDumpProto.DIRTY, mDirty);
- proto.write(PowerServiceDumpProto.WAKEFULNESS, mWakefulness);
- proto.write(PowerServiceDumpProto.IS_WAKEFULNESS_CHANGING, mWakefulnessChanging);
- proto.write(PowerServiceDumpProto.IS_POWERED, mIsPowered);
- proto.write(PowerServiceDumpProto.PLUG_TYPE, mPlugType);
- proto.write(PowerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel);
+ proto.write(PowerManagerServiceDumpProto.DIRTY, mDirty);
+ proto.write(PowerManagerServiceDumpProto.WAKEFULNESS, mWakefulness);
+ proto.write(PowerManagerServiceDumpProto.IS_WAKEFULNESS_CHANGING, mWakefulnessChanging);
+ proto.write(PowerManagerServiceDumpProto.IS_POWERED, mIsPowered);
+ proto.write(PowerManagerServiceDumpProto.PLUG_TYPE, mPlugType);
+ proto.write(PowerManagerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel);
proto.write(
- PowerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED,
+ PowerManagerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED,
mBatteryLevelWhenDreamStarted);
- proto.write(PowerServiceDumpProto.DOCK_STATE, mDockState);
- proto.write(PowerServiceDumpProto.IS_STAY_ON, mStayOn);
- proto.write(PowerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
- proto.write(PowerServiceDumpProto.IS_BOOT_COMPLETED, mBootCompleted);
- proto.write(PowerServiceDumpProto.IS_SYSTEM_READY, mSystemReady);
+ proto.write(PowerManagerServiceDumpProto.DOCK_STATE, mDockState);
+ proto.write(PowerManagerServiceDumpProto.IS_STAY_ON, mStayOn);
+ proto.write(PowerManagerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
+ proto.write(PowerManagerServiceDumpProto.IS_BOOT_COMPLETED, mBootCompleted);
+ proto.write(PowerManagerServiceDumpProto.IS_SYSTEM_READY, mSystemReady);
proto.write(
- PowerServiceDumpProto.IS_HAL_AUTO_SUSPEND_MODE_ENABLED,
+ PowerManagerServiceDumpProto.IS_HAL_AUTO_SUSPEND_MODE_ENABLED,
mHalAutoSuspendModeEnabled);
proto.write(
- PowerServiceDumpProto.IS_HAL_AUTO_INTERACTIVE_MODE_ENABLED,
+ PowerManagerServiceDumpProto.IS_HAL_AUTO_INTERACTIVE_MODE_ENABLED,
mHalInteractiveModeEnabled);
- final long activeWakeLocksToken = proto.start(PowerServiceDumpProto.ACTIVE_WAKE_LOCKS);
+ final long activeWakeLocksToken = proto.start(PowerManagerServiceDumpProto.ACTIVE_WAKE_LOCKS);
proto.write(
- PowerServiceDumpProto.ActiveWakeLocksProto.IS_CPU,
+ PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_CPU,
(mWakeLockSummary & WAKE_LOCK_CPU) != 0);
proto.write(
- PowerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_BRIGHT,
+ PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_BRIGHT,
(mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0);
proto.write(
- PowerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_DIM,
+ PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_DIM,
(mWakeLockSummary & WAKE_LOCK_SCREEN_DIM) != 0);
proto.write(
- PowerServiceDumpProto.ActiveWakeLocksProto.IS_BUTTON_BRIGHT,
+ PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_BUTTON_BRIGHT,
(mWakeLockSummary & WAKE_LOCK_BUTTON_BRIGHT) != 0);
proto.write(
- PowerServiceDumpProto.ActiveWakeLocksProto.IS_PROXIMITY_SCREEN_OFF,
+ PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_PROXIMITY_SCREEN_OFF,
(mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0);
proto.write(
- PowerServiceDumpProto.ActiveWakeLocksProto.IS_STAY_AWAKE,
+ PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_STAY_AWAKE,
(mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0);
proto.write(
- PowerServiceDumpProto.ActiveWakeLocksProto.IS_DOZE,
+ PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_DOZE,
(mWakeLockSummary & WAKE_LOCK_DOZE) != 0);
proto.write(
- PowerServiceDumpProto.ActiveWakeLocksProto.IS_DRAW,
+ PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_DRAW,
(mWakeLockSummary & WAKE_LOCK_DRAW) != 0);
proto.end(activeWakeLocksToken);
- proto.write(PowerServiceDumpProto.NOTIFY_LONG_SCHEDULED_MS, mNotifyLongScheduled);
- proto.write(PowerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched);
- proto.write(PowerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck);
+ proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_SCHEDULED_MS, mNotifyLongScheduled);
+ proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched);
+ proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck);
- final long userActivityToken = proto.start(PowerServiceDumpProto.USER_ACTIVITY);
+ final long userActivityToken = proto.start(PowerManagerServiceDumpProto.USER_ACTIVITY);
proto.write(
- PowerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT,
+ PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT,
(mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0);
proto.write(
- PowerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM,
+ PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM,
(mUserActivitySummary & USER_ACTIVITY_SCREEN_DIM) != 0);
proto.write(
- PowerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM,
+ PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM,
(mUserActivitySummary & USER_ACTIVITY_SCREEN_DREAM) != 0);
proto.end(userActivityToken);
proto.write(
- PowerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY,
+ PowerManagerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY,
mRequestWaitForNegativeProximity);
- proto.write(PowerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled);
- proto.write(PowerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned);
- proto.write(PowerServiceDumpProto.IS_LOW_POWER_MODE_ENABLED, mLowPowerModeEnabled);
- proto.write(PowerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow);
- proto.write(PowerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode);
- proto.write(PowerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode);
+ proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled);
+ proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned);
+ proto.write(PowerManagerServiceDumpProto.IS_LOW_POWER_MODE_ENABLED, mLowPowerModeEnabled);
+ proto.write(PowerManagerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow);
+ proto.write(PowerManagerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode);
+ proto.write(PowerManagerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode);
for (int id : mDeviceIdleWhitelist) {
- proto.write(PowerServiceDumpProto.DEVICE_IDLE_WHITELIST, id);
+ proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_WHITELIST, id);
}
for (int id : mDeviceIdleTempWhitelist) {
- proto.write(PowerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id);
+ proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id);
}
- proto.write(PowerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime);
- proto.write(PowerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime);
- proto.write(PowerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime);
+ proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime);
+ proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime);
+ proto.write(PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime);
proto.write(
- PowerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS,
+ PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS,
mLastUserActivityTimeNoChangeLights);
proto.write(
- PowerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS,
+ PowerManagerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS,
mLastInteractivePowerHintTime);
proto.write(
- PowerServiceDumpProto.LAST_SCREEN_BRIGHTNESS_BOOST_TIME_MS,
+ PowerManagerServiceDumpProto.LAST_SCREEN_BRIGHTNESS_BOOST_TIME_MS,
mLastScreenBrightnessBoostTime);
proto.write(
- PowerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS,
+ PowerManagerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS,
mScreenBrightnessBoostInProgress);
- proto.write(PowerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady);
+ proto.write(PowerManagerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady);
proto.write(
- PowerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER,
+ PowerManagerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER,
mHoldingWakeLockSuspendBlocker);
proto.write(
- PowerServiceDumpProto.IS_HOLDING_DISPLAY_SUSPEND_BLOCKER,
+ PowerManagerServiceDumpProto.IS_HOLDING_DISPLAY_SUSPEND_BLOCKER,
mHoldingDisplaySuspendBlocker);
final long settingsAndConfigurationToken =
- proto.start(PowerServiceDumpProto.SETTINGS_AND_CONFIGURATION);
+ proto.start(PowerManagerServiceDumpProto.SETTINGS_AND_CONFIGURATION);
proto.write(
PowerServiceSettingsAndConfigurationDumpProto
.IS_DECOUPLE_HAL_AUTO_SUSPEND_MODE_FROM_DISPLAY_CONFIG,
@@ -3698,42 +3694,43 @@
final int sleepTimeout = getSleepTimeoutLocked();
final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
- proto.write(PowerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout);
- proto.write(PowerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout);
- proto.write(PowerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration);
- proto.write(PowerServiceDumpProto.ARE_UIDS_CHANGING, mUidsChanging);
- proto.write(PowerServiceDumpProto.ARE_UIDS_CHANGED, mUidsChanged);
+ proto.write(PowerManagerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout);
+ proto.write(PowerManagerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout);
+ proto.write(PowerManagerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration);
+ proto.write(PowerManagerServiceDumpProto.ARE_UIDS_CHANGING, mUidsChanging);
+ proto.write(PowerManagerServiceDumpProto.ARE_UIDS_CHANGED, mUidsChanged);
for (int i = 0; i < mUidState.size(); i++) {
final UidState state = mUidState.valueAt(i);
- final long uIDToken = proto.start(PowerServiceDumpProto.UIDS);
+ final long uIDToken = proto.start(PowerManagerServiceDumpProto.UID_STATES);
final int uid = mUidState.keyAt(i);
- proto.write(PowerServiceDumpProto.UidProto.UID, uid);
- proto.write(PowerServiceDumpProto.UidProto.UID_STRING, UserHandle.formatUid(uid));
- proto.write(PowerServiceDumpProto.UidProto.IS_ACTIVE, state.mActive);
- proto.write(PowerServiceDumpProto.UidProto.NUM_WAKE_LOCKS, state.mNumWakeLocks);
+ proto.write(PowerManagerServiceDumpProto.UidStateProto.UID, uid);
+ proto.write(PowerManagerServiceDumpProto.UidStateProto.UID_STRING, UserHandle.formatUid(uid));
+ proto.write(PowerManagerServiceDumpProto.UidStateProto.IS_ACTIVE, state.mActive);
+ proto.write(PowerManagerServiceDumpProto.UidStateProto.NUM_WAKE_LOCKS, state.mNumWakeLocks);
if (state.mProcState == ActivityManager.PROCESS_STATE_UNKNOWN) {
- proto.write(PowerServiceDumpProto.UidProto.IS_PROCESS_STATE_UNKNOWN, true);
+ proto.write(PowerManagerServiceDumpProto.UidStateProto.IS_PROCESS_STATE_UNKNOWN, true);
} else {
- proto.write(PowerServiceDumpProto.UidProto.PROCESS_STATE, state.mProcState);
+ proto.write(PowerManagerServiceDumpProto.UidStateProto.PROCESS_STATE,
+ ActivityManager.processStateAmToProto(state.mProcState));
}
proto.end(uIDToken);
}
- mHandler.getLooper().writeToProto(proto, PowerServiceDumpProto.LOOPER);
+ mHandler.getLooper().writeToProto(proto, PowerManagerServiceDumpProto.LOOPER);
for (WakeLock wl : mWakeLocks) {
- wl.writeToProto(proto, PowerServiceDumpProto.WAKE_LOCKS);
+ wl.writeToProto(proto, PowerManagerServiceDumpProto.WAKE_LOCKS);
}
for (SuspendBlocker sb : mSuspendBlockers) {
- sb.writeToProto(proto, PowerServiceDumpProto.SUSPEND_BLOCKERS);
+ sb.writeToProto(proto, PowerManagerServiceDumpProto.SUSPEND_BLOCKERS);
}
wcd = mWirelessChargerDetector;
}
if (wcd != null) {
- wcd.writeToProto(proto, PowerServiceDumpProto.WIRELESS_CHARGER_DETECTOR);
+ wcd.writeToProto(proto, PowerManagerServiceDumpProto.WIRELESS_CHARGER_DETECTOR);
}
proto.flush();
}
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 515fa39..755c5f0 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -34,8 +34,6 @@
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.media.AudioAttributes;
-import android.nfc.INfcAdapter;
-import android.nfc.NfcAdapter;
import android.os.FileUtils;
import android.os.Handler;
import android.os.PowerManager;
@@ -124,7 +122,6 @@
private static String METRIC_RADIOS = "shutdown_radios";
private static String METRIC_BT = "shutdown_bt";
private static String METRIC_RADIO = "shutdown_radio";
- private static String METRIC_NFC = "shutdown_nfc";
private static String METRIC_SM = "shutdown_storage_manager";
private final Object mActionDoneSync = new Object();
@@ -629,29 +626,14 @@
Thread t = new Thread() {
public void run() {
TimingsTraceLog shutdownTimingsTraceLog = newTimingsLog();
- boolean nfcOff;
boolean bluetoothReadyForShutdown;
boolean radioOff;
- final INfcAdapter nfc =
- INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
final ITelephony phone =
ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
final IBluetoothManager bluetooth =
IBluetoothManager.Stub.asInterface(ServiceManager.checkService(
BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE));
- try {
- nfcOff = nfc == null ||
- nfc.getState() == NfcAdapter.STATE_OFF;
- if (!nfcOff) {
- Log.w(TAG, "Turning off NFC...");
- metricStarted(METRIC_NFC);
- nfc.disable(false); // Don't persist new state
- }
- } catch (RemoteException ex) {
- Log.e(TAG, "RemoteException during NFC shutdown", ex);
- nfcOff = true;
- }
try {
bluetoothReadyForShutdown = bluetooth == null ||
@@ -678,7 +660,7 @@
radioOff = true;
}
- Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
+ Log.i(TAG, "Waiting for Bluetooth and Radio...");
long delay = endTime - SystemClock.elapsedRealtime();
while (delay > 0) {
@@ -722,23 +704,9 @@
.logDuration("ShutdownRadio", TRON_METRICS.get(METRIC_RADIO));
}
}
- if (!nfcOff) {
- try {
- nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
- } catch (RemoteException ex) {
- Log.e(TAG, "RemoteException during NFC shutdown", ex);
- nfcOff = true;
- }
- if (nfcOff) {
- Log.i(TAG, "NFC turned off.");
- metricEnded(METRIC_NFC);
- shutdownTimingsTraceLog
- .logDuration("ShutdownNfc", TRON_METRICS.get(METRIC_NFC));
- }
- }
- if (radioOff && bluetoothReadyForShutdown && nfcOff) {
- Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
+ if (radioOff && bluetoothReadyForShutdown) {
+ Log.i(TAG, "Radio and Bluetooth shutdown complete.");
done[0] = true;
break;
}
@@ -755,7 +723,7 @@
} catch (InterruptedException ex) {
}
if (!done[0]) {
- Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown.");
+ Log.w(TAG, "Timed out waiting for Radio and Bluetooth shutdown.");
}
}
diff --git a/services/core/java/com/android/server/power/WirelessChargerDetector.java b/services/core/java/com/android/server/power/WirelessChargerDetector.java
index 6ee9dcd..54487e3 100644
--- a/services/core/java/com/android/server/power/WirelessChargerDetector.java
+++ b/services/core/java/com/android/server/power/WirelessChargerDetector.java
@@ -24,7 +24,6 @@
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
-import android.service.power.WirelessChargerDetectorProto;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 22d2bcf..41534cb 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -24,7 +24,8 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
-import android.content.IntentFilter;
+import android.net.NetworkStats;
+import android.os.BatteryStatsInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -37,15 +38,17 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
+import android.util.StatsLog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
import java.util.ArrayList;
import java.util.List;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.KernelWakelockReader;
-import com.android.internal.os.KernelWakelockStats;
-import com.android.server.SystemService;
-
import java.util.Map;
/**
@@ -65,7 +68,7 @@
private static final Object sStatsdLock = new Object();
private final PendingIntent mAnomalyAlarmIntent;
- private final PendingIntent mPollingAlarmIntent;
+ private final PendingIntent mPullingAlarmIntent;
private final BroadcastReceiver mAppUpdateReceiver;
private final BroadcastReceiver mUserUpdateReceiver;
@@ -76,8 +79,8 @@
mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
new Intent(mContext, AnomalyAlarmReceiver.class), 0);
- mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
- new Intent(mContext, PollingAlarmReceiver.class), 0);
+ mPullingAlarmIntent = PendingIntent.getBroadcast(
+ mContext, 0, new Intent(mContext, PullingAlarmReceiver.class), 0);
mAppUpdateReceiver = new AppUpdateReceiver();
mUserUpdateReceiver = new BroadcastReceiver() {
@Override
@@ -144,15 +147,16 @@
public final static class AppUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
/**
* App updates actually consist of REMOVE, ADD, and then REPLACE broadcasts. To avoid
* waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
+ * If we can't find the value for EXTRA_REPLACING, we default to false.
*/
- if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) &&
- intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)
+ && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
return; // Keep only replacing or normal add and remove.
}
+ Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
synchronized (sStatsdLock) {
if (sStatsd == null) {
Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
@@ -205,24 +209,25 @@
}
}
- public final static class PollingAlarmReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) Slog.d(TAG, "Time to poll something.");
- synchronized (sStatsdLock) {
- if (sStatsd == null) {
- Slog.w(TAG, "Could not access statsd to inform it of polling alarm firing");
- return;
- }
- try {
- // Two-way call to statsd to retain AlarmManager wakelock
- sStatsd.informPollAlarmFired();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to inform statsd of polling alarm firing", e);
- }
- }
- // AlarmManager releases its own wakelock here.
+ public final static class PullingAlarmReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG)
+ Slog.d(TAG, "Time to poll something.");
+ synchronized (sStatsdLock) {
+ if (sStatsd == null) {
+ Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing");
+ return;
+ }
+ try {
+ // Two-way call to statsd to retain AlarmManager wakelock
+ sStatsd.informPollAlarmFired();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to inform statsd of pulling alarm firing", e);
+ }
}
+ // AlarmManager releases its own wakelock here.
+ }
}
@Override // Binder call
@@ -253,32 +258,32 @@
}
@Override // Binder call
- public void setPollingAlarms(long timestampMs, long intervalMs) {
- enforceCallingPermission();
- if (DEBUG) Slog.d(TAG, "Setting polling alarm for " + timestampMs
- + " every " + intervalMs + "ms");
- final long callingToken = Binder.clearCallingIdentity();
- try {
- // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
- // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
- // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
- mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs,
- mPollingAlarmIntent);
- } finally {
- Binder.restoreCallingIdentity(callingToken);
- }
+ public void setPullingAlarms(long timestampMs, long intervalMs) {
+ enforceCallingPermission();
+ if (DEBUG)
+ Slog.d(TAG, "Setting pulling alarm for " + timestampMs + " every " + intervalMs + "ms");
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+ // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+ // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
+ mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs, mPullingAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
}
@Override // Binder call
- public void cancelPollingAlarms() {
- enforceCallingPermission();
- if (DEBUG) Slog.d(TAG, "Cancelling polling alarm");
- final long callingToken = Binder.clearCallingIdentity();
- try {
- mAlarmManager.cancel(mPollingAlarmIntent);
- } finally {
- Binder.restoreCallingIdentity(callingToken);
- }
+ public void cancelPullingAlarms() {
+ enforceCallingPermission();
+ if (DEBUG)
+ Slog.d(TAG, "Cancelling pulling alarm");
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ mAlarmManager.cancel(mPullingAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
}
// These values must be kept in sync with cmd/statsd/StatsPullerManager.h.
@@ -288,35 +293,168 @@
private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+ private StatsLogEventWrapper[] addNetworkStats(int tag, NetworkStats stats, boolean withFGBG) {
+ List<StatsLogEventWrapper> ret = new ArrayList<>();
+ int size = stats.size();
+ NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
+ for (int j = 0; j < size; j++) {
+ stats.getValues(j, entry);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tag, withFGBG ? 6 : 5);
+ e.writeInt(entry.uid);
+ if (withFGBG) {
+ e.writeInt(entry.set);
+ }
+ e.writeLong(entry.rxBytes);
+ e.writeLong(entry.rxPackets);
+ e.writeLong(entry.txBytes);
+ e.writeLong(entry.txPackets);
+ ret.add(e);
+ }
+ return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+ }
+
+ /**
+ * Allows rollups per UID but keeping the set (foreground/background) slicing.
+ * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
+ */
+ private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
+ final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
+
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ entry.iface = NetworkStats.IFACE_ALL;
+ entry.tag = NetworkStats.TAG_NONE;
+ entry.metered = NetworkStats.METERED_ALL;
+ entry.roaming = NetworkStats.ROAMING_ALL;
+
+ int size = stats.size();
+ NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
+ for (int i = 0; i < size; i++) {
+ stats.getValues(i, recycle);
+
+ // Skip specific tags, since already counted in TAG_NONE
+ if (recycle.tag != NetworkStats.TAG_NONE) continue;
+
+ entry.set = recycle.set; // Allows slicing by background/foreground
+ entry.uid = recycle.uid;
+ entry.rxBytes = recycle.rxBytes;
+ entry.rxPackets = recycle.rxPackets;
+ entry.txBytes = recycle.txBytes;
+ entry.txPackets = recycle.txPackets;
+ // Operations purposefully omitted since we don't use them for statsd.
+ ret.combineValues(entry);
+ }
+ return ret;
+ }
+
@Override // Binder call
public StatsLogEventWrapper[] pullData(int pullCode) {
enforceCallingPermission();
- if (DEBUG) {
+ if (DEBUG)
Slog.d(TAG, "Pulling " + pullCode);
- }
- List<StatsLogEventWrapper> ret = new ArrayList<>();
switch (pullCode) {
- case PULL_CODE_KERNEL_WAKELOCKS: {
+ case StatsLog.WIFI_BYTES_TRANSFERRED: {
+ long token = Binder.clearCallingIdentity();
+ try {
+ // TODO: Consider caching the following call to get BatteryStatsInternal.
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getWifiIfaces();
+ if (ifaces.length == 0) {
+ return null;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ // Combine all the metrics per Uid into one record.
+ NetworkStats stats = nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+ NetworkStats.TAG_NONE, null).groupedByUid();
+ return addNetworkStats(pullCode, stats, false);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ break;
+ }
+ case StatsLog.MOBILE_BYTES_TRANSFERRED: {
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getMobileIfaces();
+ if (ifaces.length == 0) {
+ return null;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ // Combine all the metrics per Uid into one record.
+ NetworkStats stats = nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+ NetworkStats.TAG_NONE, null).groupedByUid();
+ return addNetworkStats(pullCode, stats, false);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ break;
+ }
+ case StatsLog.WIFI_BYTES_TRANSFERRED_BY_FG_BG: {
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getWifiIfaces();
+ if (ifaces.length == 0) {
+ return null;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ NetworkStats stats = rollupNetworkStatsByFGBG(
+ nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+ NetworkStats.TAG_NONE, null));
+ return addNetworkStats(pullCode, stats, true);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ break;
+ }
+ case StatsLog.MOBILE_BYTES_TRANSFERRED_BY_FG_BG: {
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getMobileIfaces();
+ if (ifaces.length == 0) {
+ return null;
+ }
+ NetworkStatsFactory nsf = new NetworkStatsFactory();
+ NetworkStats stats = rollupNetworkStatsByFGBG(
+ nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+ NetworkStats.TAG_NONE, null));
+ return addNetworkStats(pullCode, stats, true);
+ } catch (java.io.IOException e) {
+ Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ break;
+ }
+ case StatsLog.KERNEL_WAKELOCKS_REPORTED: {
final KernelWakelockStats wakelockStats =
mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+ List<StatsLogEventWrapper> ret = new ArrayList();
for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
String name = ent.getKey();
KernelWakelockStats.Entry kws = ent.getValue();
- StatsLogEventWrapper e = new StatsLogEventWrapper(101, 4);
+ StatsLogEventWrapper e = new StatsLogEventWrapper(pullCode, 4);
+ e.writeString(name);
e.writeInt(kws.mCount);
e.writeInt(kws.mVersion);
e.writeLong(kws.mTotalTime);
- e.writeString(name);
ret.add(e);
}
- break;
+ return ret.toArray(new StatsLogEventWrapper[ret.size()]);
}
default:
Slog.w(TAG, "No such pollable data as " + pullCode);
return null;
}
- return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+ return null;
}
@Override // Binder call
@@ -440,7 +578,7 @@
mContext.unregisterReceiver(mAppUpdateReceiver);
mContext.unregisterReceiver(mUserUpdateReceiver);
cancelAnomalyAlarm();
- cancelPollingAlarms();
+ cancelPullingAlarms();
}
}
diff --git a/services/core/java/com/android/server/utils/PriorityDump.java b/services/core/java/com/android/server/utils/PriorityDump.java
index 054f156..fb92c2b8 100644
--- a/services/core/java/com/android/server/utils/PriorityDump.java
+++ b/services/core/java/com/android/server/utils/PriorityDump.java
@@ -16,12 +16,19 @@
package com.android.server.utils;
+import android.annotation.IntDef;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
/**
* Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the
- * {@link #PRIORITY_ARG} argument.
+ * {@link #PRIORITY_ARG} and {@link #PROTO_ARG} arguments.
* <p>
* Typical usage:
*
@@ -31,13 +38,25 @@
private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
@Override
- public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("Donuts in the box: 1");
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ if (asProto) {
+ ProtoOutputStream proto = new ProtoOutputStream(fd);
+ proto.write(SpringfieldProto.DONUTS, 1);
+ proto.flush();
+ } else {
+ pw.println("Donuts in the box: 1");
+ }
}
@Override
public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("Nuclear reactor status: DANGER - MELTDOWN IMMINENT");
+ if (asProto) {
+ ProtoOutputStream proto = new ProtoOutputStream(fd);
+ proto.write(SpringfieldProto.REACTOR_STATUS, DANGER_MELTDOWN_IMMINENT);
+ proto.flush();
+ } else {
+ pw.println("Nuclear reactor status: DANGER - MELTDOWN IMMINENT");
+ }
}
};
@@ -65,6 +84,9 @@
$ adb shell dumpsys snpp --dump-priority NORMAL
Nuclear reactor status: DANGER - MELTDOWN IMMINENT
+ $ adb shell dumpsys snpp --dump-priority CRITICAL --proto
+ //binary output
+
* </code></pre>
*
*
@@ -85,95 +107,146 @@
public final class PriorityDump {
public static final String PRIORITY_ARG = "--dump-priority";
+ public static final String PROTO_ARG = "--proto";
private PriorityDump() {
throw new UnsupportedOperationException();
}
+ /** Enum to switch through supported priority types */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({PRIORITY_TYPE_INVALID, PRIORITY_TYPE_CRITICAL, PRIORITY_TYPE_HIGH,
+ PRIORITY_TYPE_NORMAL})
+ private @interface PriorityType { }
+ private static final int PRIORITY_TYPE_INVALID = 0;
+ private static final int PRIORITY_TYPE_CRITICAL = 1;
+ private static final int PRIORITY_TYPE_HIGH = 2;
+ private static final int PRIORITY_TYPE_NORMAL = 3;
+
/**
- * Parses {@code} and call the proper {@link PriorityDumper} method when the first argument is
- * {@code --dump-priority}, stripping the priority and its type.
+ * Parses {@code args} matching {@code --dump-priority} and/or {@code --proto}. The matching
+ * arguments are stripped.
+ * <p>
+ * If priority args are passed as an argument, it will call the appropriate method and if proto
+ * args are passed then the {@code asProto} flag is set.
* <p>
* For example, if called as {@code --dump-priority HIGH arg1 arg2 arg3}, it will call
- * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}) </code>
+ * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}, false) </code>
* <p>
* If the {@code --dump-priority} is not set, it calls
- * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[])} passing the whole
+ * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[], boolean)} passing the whole
* {@code args} instead.
*/
public static void dump(PriorityDumper dumper, FileDescriptor fd, PrintWriter pw,
String[] args) {
- if (args != null && args.length >= 2 && args[0].equals(PRIORITY_ARG)) {
- final String priority = args[1];
- switch (priority) {
- case "CRITICAL": {
- dumper.dumpCritical(fd, pw, getStrippedArgs(args));
- return;
+ boolean asProto = false;
+ @PriorityType int priority = PRIORITY_TYPE_INVALID;
+
+ if (args == null) {
+ dumper.dump(fd, pw, args, asProto);
+ return;
+ }
+
+ String[] strippedArgs = new String[args.length];
+ int strippedCount = 0;
+ for (int argIndex = 0; argIndex < args.length; argIndex++) {
+ if (args[argIndex].equals(PROTO_ARG)) {
+ asProto = true;
+ } else if (args[argIndex].equals(PRIORITY_ARG)) {
+ if (argIndex + 1 < args.length) {
+ argIndex++;
+ priority = getPriorityType(args[argIndex]);
}
- case "HIGH": {
- dumper.dumpHigh(fd, pw, getStrippedArgs(args));
- return;
- }
- case "NORMAL": {
- dumper.dumpNormal(fd, pw, getStrippedArgs(args));
- return;
- }
+ } else {
+ strippedArgs[strippedCount++] = args[argIndex];
}
}
- dumper.dump(fd, pw, args);
+
+ if (strippedCount < args.length) {
+ strippedArgs = Arrays.copyOf(strippedArgs, strippedCount);
+ }
+
+ switch (priority) {
+ case PRIORITY_TYPE_CRITICAL: {
+ dumper.dumpCritical(fd, pw, strippedArgs, asProto);
+ return;
+ }
+ case PRIORITY_TYPE_HIGH: {
+ dumper.dumpHigh(fd, pw, strippedArgs, asProto);
+ return;
+ }
+ case PRIORITY_TYPE_NORMAL: {
+ dumper.dumpNormal(fd, pw, strippedArgs, asProto);
+ return;
+ }
+ default: {
+ dumper.dump(fd, pw, strippedArgs, asProto);
+ return;
+ }
+ }
}
/**
- * Gets an array without the {@code --dump-priority PRIORITY} prefix.
+ * Converts priority argument type to enum.
*/
- private static String[] getStrippedArgs(String[] args) {
- final String[] stripped = new String[args.length - 2];
- System.arraycopy(args, 2, stripped, 0, stripped.length);
- return stripped;
+ private static @PriorityType int getPriorityType(String arg) {
+ switch (arg) {
+ case "CRITICAL": {
+ return PRIORITY_TYPE_CRITICAL;
+ }
+ case "HIGH": {
+ return PRIORITY_TYPE_HIGH;
+ }
+ case "NORMAL": {
+ return PRIORITY_TYPE_NORMAL;
+ }
+ }
+ return PRIORITY_TYPE_INVALID;
}
/**
* Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the
- * {@link #PRIORITY_ARG} argument.
+ * {@link #PRIORITY_ARG} and {@link #PROTO_ARG} arguments.
*
* @hide
*/
- public static interface PriorityDumper {
+ public interface PriorityDumper {
/**
* Dumps only the critical section.
*/
@SuppressWarnings("unused")
- default void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+ default void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean asProto) {
}
/**
* Dumps only the high-priority section.
*/
@SuppressWarnings("unused")
- default void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args) {
+ default void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
}
/**
* Dumps only the normal section.
*/
@SuppressWarnings("unused")
- default void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ default void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
}
/**
* Dumps all sections.
* <p>
* This method is called when
- * {@link PriorityDump#dump(PriorityDumper, FileDescriptor, PrintWriter, String[])} is
- * called without priority arguments. By default, it calls the 3 {@code dumpTYPE} methods,
- * so sub-classes just need to implement the priority types they support.
+ * {@link PriorityDump#dump(PriorityDumper, FileDescriptor, PrintWriter, String[], boolean)}
+ * is called without priority arguments. By default, it calls the 3 {@code dumpTYPE}
+ * methods, so sub-classes just need to implement the priority types they support.
*/
@SuppressWarnings("unused")
- default void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- dumpCritical(fd, pw, args);
- dumpHigh(fd, pw, args);
- dumpNormal(fd, pw, args);
+ default void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ dumpCritical(fd, pw, args, asProto);
+ dumpHigh(fd, pw, args, asProto);
+ dumpNormal(fd, pw, args, asProto);
}
}
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 6484a13..de4fd7cd 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -74,12 +74,12 @@
*/
final class AccessibilityController {
- private final WindowManagerService mWindowManagerService;
+ private final WindowManagerService mService;
private static final float[] sTempFloats = new float[9];
public AccessibilityController(WindowManagerService service) {
- mWindowManagerService = service;
+ mService = service;
}
private DisplayMagnifier mDisplayMagnifier;
@@ -91,7 +91,7 @@
if (mDisplayMagnifier != null) {
throw new IllegalStateException("Magnification callbacks already set!");
}
- mDisplayMagnifier = new DisplayMagnifier(mWindowManagerService, callbacks);
+ mDisplayMagnifier = new DisplayMagnifier(mService, callbacks);
} else {
if (mDisplayMagnifier == null) {
throw new IllegalStateException("Magnification callbacks already cleared!");
@@ -108,7 +108,7 @@
"Windows for accessibility callback already set!");
}
mWindowsForAccessibilityObserver = new WindowsForAccessibilityObserver(
- mWindowManagerService, callback);
+ mService, callback);
} else {
if (mWindowsForAccessibilityObserver == null) {
throw new IllegalStateException(
@@ -120,7 +120,7 @@
public void performComputeChangedWindowsNotLocked() {
WindowsForAccessibilityObserver observer = null;
- synchronized (mWindowManagerService) {
+ synchronized (mService) {
observer = mWindowsForAccessibilityObserver;
}
if (observer != null) {
@@ -188,7 +188,7 @@
// Not relevant for the display magnifier.
WindowsForAccessibilityObserver observer = null;
- synchronized (mWindowManagerService) {
+ synchronized (mService) {
observer = mWindowsForAccessibilityObserver;
}
if (observer != null) {
@@ -268,7 +268,7 @@
private final Region mTempRegion4 = new Region();
private final Context mContext;
- private final WindowManagerService mWindowManagerService;
+ private final WindowManagerService mService;
private final MagnifiedViewport mMagnifedViewport;
private final Handler mHandler;
@@ -281,9 +281,9 @@
public DisplayMagnifier(WindowManagerService windowManagerService,
MagnificationCallbacks callbacks) {
mContext = windowManagerService.mContext;
- mWindowManagerService = windowManagerService;
+ mService = windowManagerService;
mCallbacks = callbacks;
- mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+ mHandler = new MyHandler(mService.mH.getLooper());
mMagnifedViewport = new MagnifiedViewport();
mLongAnimationDuration = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longAnimTime);
@@ -292,7 +292,7 @@
public void setMagnificationSpecLocked(MagnificationSpec spec) {
mMagnifedViewport.updateMagnificationSpecLocked(spec);
mMagnifedViewport.recomputeBoundsLocked();
- mWindowManagerService.scheduleAnimationLocked();
+ mService.scheduleAnimationLocked();
}
public void setForceShowMagnifiableBoundsLocked(boolean show) {
@@ -330,7 +330,7 @@
Slog.i(LOG_TAG, "Layers changed.");
}
mMagnifedViewport.recomputeBoundsLocked();
- mWindowManagerService.scheduleAnimationLocked();
+ mService.scheduleAnimationLocked();
}
public void onRotationChangedLocked(DisplayContent displayContent) {
@@ -421,7 +421,7 @@
public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
if (spec != null && !spec.isNop()) {
- if (!mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+ if (!mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
return null;
}
}
@@ -565,7 +565,7 @@
portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
- if (mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+ if (mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
mMagnificationRegion.op(windowBounds, Region.Op.UNION);
mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
} else {
@@ -632,7 +632,7 @@
if (isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) {
setMagnifiedRegionBorderShownLocked(false, false);
final long delay = (long) (mLongAnimationDuration
- * mWindowManagerService.getWindowAnimationScaleLocked());
+ * mService.getWindowAnimationScaleLocked());
Message message = mHandler.obtainMessage(
MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
mHandler.sendMessageDelayed(message, delay);
@@ -675,7 +675,7 @@
}
private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
- final DisplayContent dc = mWindowManagerService.getDefaultDisplayContentLocked();
+ final DisplayContent dc = mService.getDefaultDisplayContentLocked();
dc.forAllWindows((w) -> {
if (w.isOnScreen() && w.isVisibleLw()
&& !w.mWinAnimator.mEnterAnimationPending) {
@@ -705,23 +705,25 @@
SurfaceControl surfaceControl = null;
try {
mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
- surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession,
- SURFACE_TITLE, mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT,
- SurfaceControl.HIDDEN);
+ surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+ .setName(SURFACE_TITLE)
+ .setSize(mTempPoint.x, mTempPoint.y) // not a typo
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
} catch (OutOfResourcesException oore) {
/* ignore */
}
mSurfaceControl = surfaceControl;
mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
.getLayerStack());
- mSurfaceControl.setLayer(mWindowManagerService.mPolicy.getWindowLayerFromTypeLw(
+ mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw(
TYPE_MAGNIFICATION_OVERLAY)
* WindowManagerService.TYPE_LAYER_MULTIPLIER);
mSurfaceControl.setPosition(0, 0);
mSurface.copyFrom(mSurfaceControl);
mAnimationController = new AnimationController(context,
- mWindowManagerService.mH.getLooper());
+ mService.mH.getLooper());
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
@@ -736,7 +738,7 @@
}
public void setShown(boolean shown, boolean animate) {
- synchronized (mWindowManagerService.mWindowMap) {
+ synchronized (mService.mWindowMap) {
if (mShown == shown) {
return;
}
@@ -751,13 +753,13 @@
@SuppressWarnings("unused")
// Called reflectively from an animator.
public int getAlpha() {
- synchronized (mWindowManagerService.mWindowMap) {
+ synchronized (mService.mWindowMap) {
return mAlpha;
}
}
public void setAlpha(int alpha) {
- synchronized (mWindowManagerService.mWindowMap) {
+ synchronized (mService.mWindowMap) {
if (mAlpha == alpha) {
return;
}
@@ -770,7 +772,7 @@
}
public void setBounds(Region bounds) {
- synchronized (mWindowManagerService.mWindowMap) {
+ synchronized (mService.mWindowMap) {
if (mBounds.equals(bounds)) {
return;
}
@@ -783,7 +785,7 @@
}
public void updateSize() {
- synchronized (mWindowManagerService.mWindowMap) {
+ synchronized (mService.mWindowMap) {
mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
mSurfaceControl.setSize(mTempPoint.x, mTempPoint.y);
invalidate(mDirtyRect);
@@ -797,12 +799,12 @@
mDirtyRect.setEmpty();
}
mInvalidated = true;
- mWindowManagerService.scheduleAnimationLocked();
+ mService.scheduleAnimationLocked();
}
/** NOTE: This has to be called within a surface transaction. */
public void drawIfNeeded() {
- synchronized (mWindowManagerService.mWindowMap) {
+ synchronized (mService.mWindowMap) {
if (!mInvalidated) {
return;
}
@@ -950,11 +952,11 @@
} break;
case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
- synchronized (mWindowManagerService.mWindowMap) {
+ synchronized (mService.mWindowMap) {
if (mMagnifedViewport.isMagnifyingLocked()
|| isForceShowingMagnifiableBoundsLocked()) {
mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
- mWindowManagerService.scheduleAnimationLocked();
+ mService.scheduleAnimationLocked();
}
}
} break;
@@ -995,7 +997,7 @@
private final Context mContext;
- private final WindowManagerService mWindowManagerService;
+ private final WindowManagerService mService;
private final Handler mHandler;
@@ -1006,9 +1008,9 @@
public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
WindowsForAccessibilityCallback callback) {
mContext = windowManagerService.mContext;
- mWindowManagerService = windowManagerService;
+ mService = windowManagerService;
mCallback = callback;
- mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+ mHandler = new MyHandler(mService.mH.getLooper());
mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
.getSendRecurringAccessibilityEventsInterval();
computeChangedWindows();
@@ -1034,11 +1036,11 @@
boolean windowsChanged = false;
List<WindowInfo> windows = new ArrayList<WindowInfo>();
- synchronized (mWindowManagerService.mWindowMap) {
+ synchronized (mService.mWindowMap) {
// Do not send the windows if there is no current focus as
// the window manager is still looking for where to put it.
// We will do the work when we get a focus change callback.
- if (mWindowManagerService.mCurrentFocus == null) {
+ if (mService.mCurrentFocus == null) {
return;
}
@@ -1320,7 +1322,7 @@
}
private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
- final DisplayContent dc = mWindowManagerService.getDefaultDisplayContentLocked();
+ final DisplayContent dc = mService.getDefaultDisplayContentLocked();
dc.forAllWindows((w) -> {
if (w.isVisibleLw()) {
outWindows.put(w.mLayer, w);
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 5d03493..98db80e 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -21,7 +21,6 @@
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.os.Build.VERSION_CODES.O_MR1;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -1101,52 +1100,54 @@
+ " from " + fromToken + " to " + this);
final long origId = Binder.clearCallingIdentity();
+ try {
+ // Transfer the starting window over to the new token.
+ startingData = fromToken.startingData;
+ startingSurface = fromToken.startingSurface;
+ startingDisplayed = fromToken.startingDisplayed;
+ fromToken.startingDisplayed = false;
+ startingWindow = tStartingWindow;
+ reportedVisible = fromToken.reportedVisible;
+ fromToken.startingData = null;
+ fromToken.startingSurface = null;
+ fromToken.startingWindow = null;
+ fromToken.startingMoved = true;
+ tStartingWindow.mToken = this;
+ tStartingWindow.mAppToken = this;
- // Transfer the starting window over to the new token.
- startingData = fromToken.startingData;
- startingSurface = fromToken.startingSurface;
- startingDisplayed = fromToken.startingDisplayed;
- fromToken.startingDisplayed = false;
- startingWindow = tStartingWindow;
- reportedVisible = fromToken.reportedVisible;
- fromToken.startingData = null;
- fromToken.startingSurface = null;
- fromToken.startingWindow = null;
- fromToken.startingMoved = true;
- tStartingWindow.mToken = this;
- tStartingWindow.mAppToken = this;
+ if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
+ "Removing starting " + tStartingWindow + " from " + fromToken);
+ fromToken.removeChild(tStartingWindow);
+ fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
+ fromToken.mHiddenSetFromTransferredStartingWindow = false;
+ addWindow(tStartingWindow);
- if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
- "Removing starting " + tStartingWindow + " from " + fromToken);
- fromToken.removeChild(tStartingWindow);
- fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
- fromToken.mHiddenSetFromTransferredStartingWindow = false;
- addWindow(tStartingWindow);
+ // Propagate other interesting state between the tokens. If the old token is displayed,
+ // we should immediately force the new one to be displayed. If it is animating, we need
+ // to move that animation to the new one.
+ if (fromToken.allDrawn) {
+ allDrawn = true;
+ deferClearAllDrawn = fromToken.deferClearAllDrawn;
+ }
+ if (fromToken.firstWindowDrawn) {
+ firstWindowDrawn = true;
+ }
+ if (!fromToken.hidden) {
+ hidden = false;
+ hiddenRequested = false;
+ mHiddenSetFromTransferredStartingWindow = true;
+ }
+ setClientHidden(fromToken.mClientHidden);
+ fromToken.mAppAnimator.transferCurrentAnimation(
+ mAppAnimator, tStartingWindow.mWinAnimator);
- // Propagate other interesting state between the tokens. If the old token is displayed,
- // we should immediately force the new one to be displayed. If it is animating, we need
- // to move that animation to the new one.
- if (fromToken.allDrawn) {
- allDrawn = true;
- deferClearAllDrawn = fromToken.deferClearAllDrawn;
+ mService.updateFocusedWindowLocked(
+ UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
+ getDisplayContent().setLayoutNeeded();
+ mService.mWindowPlacerLocked.performSurfacePlacement();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- if (fromToken.firstWindowDrawn) {
- firstWindowDrawn = true;
- }
- if (!fromToken.hidden) {
- hidden = false;
- hiddenRequested = false;
- mHiddenSetFromTransferredStartingWindow = true;
- }
- setClientHidden(fromToken.mClientHidden);
- fromToken.mAppAnimator.transferCurrentAnimation(
- mAppAnimator, tStartingWindow.mWinAnimator);
-
- mService.updateFocusedWindowLocked(
- UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
- getDisplayContent().setLayoutNeeded();
- mService.mWindowPlacerLocked.performSurfacePlacement();
- Binder.restoreCallingIdentity(origId);
return true;
} else if (fromToken.startingData != null) {
// The previous app was getting ready to show a
@@ -1201,15 +1202,6 @@
*/
@Override
int getOrientation(int candidate) {
- // We do not allow non-fullscreen apps to influence orientation starting in O-MR1. While we
- // do throw an exception in {@link Activity#onCreate} and
- // {@link Activity#setRequestedOrientation}, we also ignore the orientation here so that
- // other calculations aren't affected.
- if (!fillsParent() && mTargetSdk >= O_MR1) {
- // Can't specify orientation if app doesn't fill parent.
- return SCREEN_ORIENTATION_UNSET;
- }
-
if (candidate == SCREEN_ORIENTATION_BEHIND) {
// Allow app to specify orientation regardless of its visibility state if the current
// candidate want us to use orientation behind. I.e. the visible app on-top of this one
@@ -1621,10 +1613,10 @@
@CallSuper
@Override
- public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
final long token = proto.start(fieldId);
writeNameToProto(proto, NAME);
- super.writeToProto(proto, WINDOW_TOKEN);
+ super.writeToProto(proto, WINDOW_TOKEN, trim);
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index d206554..9729e50 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -50,8 +50,11 @@
int w = r-l;
int h = b-t;
- surface = new SurfaceControl(session, "BlackSurface",
- w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+ surface = new SurfaceControl.Builder(session)
+ .setName("BlackSurface")
+ .setSize(w, h)
+ .setColorLayer(true)
+ .build();
surface.setAlpha(1);
surface.setLayerStack(layerStack);
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index 85f468b..2d5d1b2 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -66,8 +66,11 @@
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x,
- mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+ ctrl = new SurfaceControl.Builder(session)
+ .setName("CircularDisplayMask")
+ .setSize(mScreenSize.x, mScreenSize.y) // not a typo
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
ctrl.setLayerStack(display.getLayerStack());
ctrl.setLayer(zOrder);
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index a4ab3ba..cc94807 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -50,6 +50,9 @@
/** Contains override configuration settings applied to this configuration container. */
private Configuration mOverrideConfiguration = new Configuration();
+ /** True if mOverrideConfiguration is not empty */
+ private boolean mHasOverrideConfiguration;
+
/**
* Contains full configuration applied to this configuration container. Corresponds to full
* parent's config with applied {@link #mOverrideConfiguration}.
@@ -101,6 +104,9 @@
* @see #mFullConfiguration
*/
public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ // Pre-compute this here, so we don't need to go through the entire Configuration when
+ // writing to proto (which has significant cost if we write a lot of empty configurations).
+ mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration);
mOverrideConfiguration.setTo(overrideConfiguration);
// Update full configuration of this container and all its children.
final ConfigurationContainer parent = getParent();
@@ -330,18 +336,23 @@
* Write to a protocol buffer output stream. Protocol buffer message definition is at
* {@link com.android.server.wm.proto.ConfigurationContainerProto}.
*
- * @param protoOutputStream Stream to write the ConfigurationContainer object to.
- * @param fieldId Field Id of the ConfigurationContainer as defined in the parent
- * message.
+ * @param proto Stream to write the ConfigurationContainer object to.
+ * @param fieldId Field Id of the ConfigurationContainer as defined in the parent
+ * message.
+ * @param trim If true, reduce amount of data written.
* @hide
*/
@CallSuper
- public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
- final long token = protoOutputStream.start(fieldId);
- mOverrideConfiguration.writeToProto(protoOutputStream, OVERRIDE_CONFIGURATION);
- mFullConfiguration.writeToProto(protoOutputStream, FULL_CONFIGURATION);
- mMergedOverrideConfiguration.writeToProto(protoOutputStream, MERGED_OVERRIDE_CONFIGURATION);
- protoOutputStream.end(token);
+ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
+ final long token = proto.start(fieldId);
+ if (!trim || mHasOverrideConfiguration) {
+ mOverrideConfiguration.writeToProto(proto, OVERRIDE_CONFIGURATION);
+ }
+ if (!trim) {
+ mFullConfiguration.writeToProto(proto, FULL_CONFIGURATION);
+ mMergedOverrideConfiguration.writeToProto(proto, MERGED_OVERRIDE_CONFIGURATION);
+ }
+ proto.end(token);
}
/**
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index 48181d3..8fb2be8 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -104,9 +104,11 @@
private void constructSurface(WindowManagerService service) {
service.openSurfaceTransaction();
try {
- mDimSurface = new SurfaceControl(service.mFxSession, mName,
- 16, 16, PixelFormat.OPAQUE,
- SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+ mDimSurface = new SurfaceControl.Builder(service.mFxSession)
+ .setName(mName)
+ .setSize(16, 16)
+ .setColorLayer(true)
+ .build();
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
" DIM " + mDimSurface + ": CREATE");
@@ -117,7 +119,7 @@
} catch (Exception e) {
Slog.e(TAG_WM, "Exception creating Dim surface", e);
} finally {
- service.closeSurfaceTransaction();
+ service.closeSurfaceTransaction("DimLayer.constructSurface");
}
}
@@ -233,7 +235,7 @@
} catch (RuntimeException e) {
Slog.w(TAG, "Failure setting size", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("DimLayer.setBounds");
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 03fdc96..2f54e0e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1083,7 +1083,7 @@
mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
} finally {
if (!inTransaction) {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setRotationUnchecked");
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked");
}
@@ -1452,17 +1452,17 @@
/**
* @return The primary split-screen stack, but only if it is visible, and {@code null} otherwise.
*/
- TaskStack getSplitScreenPrimaryStackStack() {
- TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStackStack();
+ TaskStack getSplitScreenPrimaryStack() {
+ TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStack();
return (stack != null && stack.isVisible()) ? stack : null;
}
/**
- * Like {@link #getSplitScreenPrimaryStackStack}, but also returns the stack if it's currently
+ * Like {@link #getSplitScreenPrimaryStack}, but also returns the stack if it's currently
* not visible.
*/
- TaskStack getSplitScreenPrimaryStackStackIgnoringVisibility() {
- return mTaskStackContainers.getSplitScreenPrimaryStackStack();
+ TaskStack getSplitScreenPrimaryStackIgnoringVisibility() {
+ return mTaskStackContainers.getSplitScreenPrimaryStack();
}
TaskStack getPinnedStack() {
@@ -1877,7 +1877,7 @@
mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
}
// TODO(multi-display): Support docked stacks on secondary displays.
- if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStackStack() != null) {
+ if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStack() != null) {
mDividerControllerLocked.getTouchRegion(mTmpRect);
mTmpRegion.set(mTmpRect);
mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
@@ -2137,27 +2137,27 @@
@CallSuper
@Override
- public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
final long token = proto.start(fieldId);
- super.writeToProto(proto, WINDOW_CONTAINER);
+ super.writeToProto(proto, WINDOW_CONTAINER, trim);
proto.write(ID, mDisplayId);
for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
- stack.writeToProto(proto, STACKS);
+ stack.writeToProto(proto, STACKS, trim);
}
mDividerControllerLocked.writeToProto(proto, DOCKED_STACK_DIVIDER_CONTROLLER);
mPinnedStackControllerLocked.writeToProto(proto, PINNED_STACK_CONTROLLER);
for (int i = mAboveAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
final WindowToken windowToken = mAboveAppWindowsContainers.getChildAt(i);
- windowToken.writeToProto(proto, ABOVE_APP_WINDOWS);
+ windowToken.writeToProto(proto, ABOVE_APP_WINDOWS, trim);
}
for (int i = mBelowAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
final WindowToken windowToken = mBelowAppWindowsContainers.getChildAt(i);
- windowToken.writeToProto(proto, BELOW_APP_WINDOWS);
+ windowToken.writeToProto(proto, BELOW_APP_WINDOWS, trim);
}
for (int i = mImeWindowsContainers.getChildCount() - 1; i >= 0; --i) {
final WindowToken windowToken = mImeWindowsContainers.getChildAt(i);
- windowToken.writeToProto(proto, IME_WINDOWS);
+ windowToken.writeToProto(proto, IME_WINDOWS, trim);
}
proto.write(DPI, mBaseDisplayDensity);
mDisplayInfo.writeToProto(proto, DISPLAY_INFO);
@@ -2232,7 +2232,7 @@
if (pinnedStack != null) {
pw.println(prefix + "pinnedStack=" + pinnedStack.getName());
}
- final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStackStack();
+ final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStack();
if (splitScreenPrimaryStack != null) {
pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName());
}
@@ -3401,7 +3401,7 @@
return mPinnedStack;
}
- TaskStack getSplitScreenPrimaryStackStack() {
+ TaskStack getSplitScreenPrimaryStack() {
return mSplitScreenPrimaryStack;
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 52526e2..5ea0e1d 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -137,6 +137,8 @@
float mLastDividerProgress;
private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4];
private boolean mImeHideRequested;
+ private final Rect mLastDimLayerRect = new Rect();
+ private float mLastDimLayerAlpha;
DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
@@ -319,7 +321,7 @@
if (mWindow == null) {
return;
}
- TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
// If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
final boolean visible = stack != null;
@@ -359,7 +361,7 @@
}
void positionDockedStackedDivider(Rect frame) {
- TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStack();
+ TaskStack stack = mDisplayContent.getSplitScreenPrimaryStack();
if (stack == null) {
// Unfortunately we might end up with still having a divider, even though the underlying
// stack was already removed. This is because we are on AM thread and the removal of the
@@ -456,7 +458,7 @@
long animDuration = 0;
if (animate) {
final TaskStack stack =
- mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
final long transitionDuration = isAnimationMaximizing()
? mService.mAppTransition.getLastClipRevealTransitionDuration()
: DEFAULT_APP_TRANSITION_DURATION;
@@ -511,7 +513,7 @@
mDockedStackListeners.register(listener);
notifyDockedDividerVisibilityChanged(wasVisible());
notifyDockedStackExistsChanged(
- mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null);
+ mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null);
notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
isHomeStackResizable());
notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
@@ -525,26 +527,42 @@
* display in that windowing mode.
*/
void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
- mService.openSurfaceTransaction();
// TODO: Maybe only allow split-screen windowing modes?
final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
? mDisplayContent.getStack(targetWindowingMode)
: null;
- final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStackStack();
+ final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack();
boolean visibleAndValid = visible && stack != null && dockedStack != null;
if (visibleAndValid) {
stack.getDimBounds(mTmpRect);
if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
- mDimLayer.setBounds(mTmpRect);
- mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
+ if (!mLastDimLayerRect.equals(mTmpRect) || mLastDimLayerAlpha != alpha) {
+ try {
+ // TODO: This should use the regular animation transaction - here and below
+ mService.openSurfaceTransaction();
+ mDimLayer.setBounds(mTmpRect);
+ mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
+ } finally {
+ mService.closeSurfaceTransaction("setResizeDimLayer");
+ }
+ }
+ mLastDimLayerRect.set(mTmpRect);
+ mLastDimLayerAlpha = alpha;
} else {
visibleAndValid = false;
}
}
if (!visibleAndValid) {
- mDimLayer.hide();
+ if (mLastDimLayerAlpha != 0f) {
+ try {
+ mService.openSurfaceTransaction();
+ mDimLayer.hide();
+ } finally {
+ mService.closeSurfaceTransaction("setResizeDimLayer");
+ }
+ }
+ mLastDimLayerAlpha = 0f;
}
- mService.closeSurfaceTransaction();
}
/**
@@ -598,7 +616,7 @@
}
private void checkMinimizeChanged(boolean animate) {
- if (mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() == null) {
+ if (mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() == null) {
return;
}
final TaskStack homeStack = mDisplayContent.getHomeStack();
@@ -760,7 +778,7 @@
}
private boolean setMinimizedDockedStack(boolean minimized) {
- final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
}
@@ -811,7 +829,7 @@
}
private boolean animateForMinimizedDockedStack(long now) {
- final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
if (!mAnimationStarted) {
mAnimationStarted = true;
mAnimationStartTime = now;
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
new file mode 100644
index 0000000..79d46ce
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.content.ClipData;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Slog;
+import android.view.Display;
+import android.view.IWindow;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.View;
+
+/**
+ * Managing drag and drop operations initiated by View#startDragAndDrop.
+ */
+class DragDropController {
+ private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
+ private static final long DRAG_TIMEOUT_MS = 5000;
+
+ // Messages for Handler.
+ private static final int MSG_DRAG_START_TIMEOUT = 0;
+ static final int MSG_DRAG_END_TIMEOUT = 1;
+ static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 2;
+ static final int MSG_ANIMATION_END = 3;
+
+ DragState mDragState;
+
+ private WindowManagerService mService;
+ private final Handler mHandler;
+
+ boolean dragDropActiveLocked() {
+ return mDragState != null;
+ }
+
+ DragDropController(WindowManagerService service, Looper looper) {
+ mService = service;
+ mHandler = new DragHandler(service, looper);
+ }
+
+ IBinder prepareDrag(SurfaceSession session, int callerPid,
+ int callerUid, IWindow window, int flags, int width, int height, Surface outSurface) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height
+ + " flags=" + Integer.toHexString(flags) + " win=" + window
+ + " asbinder=" + window.asBinder());
+ }
+
+ IBinder token = null;
+
+ synchronized (mService.mWindowMap) {
+ if (dragDropActiveLocked()) {
+ Slog.w(TAG_WM, "Drag already in progress");
+ return null;
+ }
+
+ // TODO(multi-display): support other displays
+ final DisplayContent displayContent =
+ mService.getDefaultDisplayContentLocked();
+ final Display display = displayContent.getDisplay();
+
+ final SurfaceControl surface = new SurfaceControl.Builder(session)
+ .setName("drag surface")
+ .setSize(width, height)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
+ surface.setLayerStack(display.getLayerStack());
+ float alpha = 1;
+ if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
+ alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
+ }
+ surface.setAlpha(alpha);
+
+ if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG " + surface + ": CREATE");
+ outSurface.copyFrom(surface);
+ final IBinder winBinder = window.asBinder();
+ token = new Binder();
+ mDragState = new DragState(mService, token, surface, flags, winBinder);
+ mDragState.mPid = callerPid;
+ mDragState.mUid = callerUid;
+ mDragState.mOriginalAlpha = alpha;
+ token = mDragState.mToken = new Binder();
+
+ // 5 second timeout for this window to actually begin the drag
+ sendTimeoutMessage(MSG_DRAG_START_TIMEOUT, winBinder);
+ }
+
+ return token;
+ }
+
+ boolean performDrag(IWindow window, IBinder dragToken,
+ int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
+ }
+
+ synchronized (mService.mWindowMap) {
+ if (mDragState == null) {
+ Slog.w(TAG_WM, "No drag prepared");
+ throw new IllegalStateException("performDrag() without prepareDrag()");
+ }
+
+ if (dragToken != mDragState.mToken) {
+ Slog.w(TAG_WM, "Performing mismatched drag");
+ throw new IllegalStateException("performDrag() does not match prepareDrag()");
+ }
+
+ final WindowState callingWin = mService.windowForClientLocked(null, window, false);
+ if (callingWin == null) {
+ Slog.w(TAG_WM, "Bad requesting window " + window);
+ return false; // !!! TODO: throw here?
+ }
+
+ // !!! TODO: if input is not still focused on the initiating window, fail
+ // the drag initiation (e.g. an alarm window popped up just as the application
+ // called performDrag()
+
+ mHandler.removeMessages(MSG_DRAG_START_TIMEOUT, window.asBinder());
+
+ // !!! TODO: extract the current touch (x, y) in screen coordinates. That
+ // will let us eliminate the (touchX,touchY) parameters from the API.
+
+ // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as
+ // the actual drag event dispatch stuff in the dragstate
+
+ final DisplayContent displayContent = callingWin.getDisplayContent();
+ if (displayContent == null) {
+ return false;
+ }
+ Display display = displayContent.getDisplay();
+ mDragState.register(display);
+ if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
+ mDragState.getInputChannel())) {
+ Slog.e(TAG_WM, "Unable to transfer touch focus");
+ mDragState.unregister();
+ mDragState.reset();
+ mDragState = null;
+ return false;
+ }
+
+ mDragState.mDisplayContent = displayContent;
+ mDragState.mData = data;
+ mDragState.broadcastDragStartedLocked(touchX, touchY);
+ mDragState.overridePointerIconLocked(touchSource);
+
+ // remember the thumb offsets for later
+ mDragState.mThumbOffsetX = thumbCenterX;
+ mDragState.mThumbOffsetY = thumbCenterY;
+
+ // Make the surface visible at the proper location
+ final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
+ TAG_WM, ">>> OPEN TRANSACTION performDrag");
+ mService.openSurfaceTransaction();
+ try {
+ surfaceControl.setPosition(touchX - thumbCenterX,
+ touchY - thumbCenterY);
+ surfaceControl.setLayer(mDragState.getDragLayerLocked());
+ surfaceControl.setLayerStack(display.getLayerStack());
+ surfaceControl.show();
+ } finally {
+ mService.closeSurfaceTransaction("performDrag");
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
+ TAG_WM, "<<< CLOSE TRANSACTION performDrag");
+ }
+
+ mDragState.notifyLocationLocked(touchX, touchY);
+ }
+
+ return true; // success!
+ }
+
+ void reportDropResult(IWindow window, boolean consumed) {
+ IBinder token = window.asBinder();
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
+ }
+
+ synchronized (mService.mWindowMap) {
+ if (mDragState == null) {
+ // Most likely the drop recipient ANRed and we ended the drag
+ // out from under it. Log the issue and move on.
+ Slog.w(TAG_WM, "Drop result given but no drag in progress");
+ return;
+ }
+
+ if (mDragState.mToken != token) {
+ // We're in a drag, but the wrong window has responded.
+ Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
+ throw new IllegalStateException("reportDropResult() by non-recipient");
+ }
+
+ // The right window has responded, even if it's no longer around,
+ // so be sure to halt the timeout even if the later WindowState
+ // lookup fails.
+ mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
+ WindowState callingWin = mService.windowForClientLocked(null, window, false);
+ if (callingWin == null) {
+ Slog.w(TAG_WM, "Bad result-reporting window " + window);
+ return; // !!! TODO: throw here?
+ }
+
+ mDragState.mDragResult = consumed;
+ mDragState.endDragLocked();
+ }
+ }
+
+ void cancelDragAndDrop(IBinder dragToken) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "cancelDragAndDrop");
+ }
+
+ synchronized (mService.mWindowMap) {
+ if (mDragState == null) {
+ Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
+ throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
+ }
+
+ if (mDragState.mToken != dragToken) {
+ Slog.w(TAG_WM,
+ "cancelDragAndDrop() does not match prepareDrag()");
+ throw new IllegalStateException(
+ "cancelDragAndDrop() does not match prepareDrag()");
+ }
+
+ mDragState.mDragResult = false;
+ mDragState.cancelDragLocked();
+ }
+ }
+
+ void dragRecipientEntered(IWindow window) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
+ }
+ }
+
+ void dragRecipientExited(IWindow window) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
+ }
+ }
+
+ /**
+ * Sends a message to the Handler managed by DragDropController.
+ */
+ void sendHandlerMessage(int what, Object arg) {
+ mHandler.obtainMessage(what, arg).sendToTarget();
+ }
+
+ /**
+ * Sends a timeout message to the Handler managed by DragDropController.
+ */
+ void sendTimeoutMessage(int what, Object arg) {
+ mHandler.removeMessages(what, arg);
+ final Message msg = mHandler.obtainMessage(what, arg);
+ mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
+ }
+
+ private class DragHandler extends Handler {
+ /**
+ * Lock for window manager.
+ */
+ private final WindowManagerService mService;
+
+ DragHandler(WindowManagerService service, Looper looper) {
+ super(looper);
+ mService = service;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DRAG_START_TIMEOUT: {
+ IBinder win = (IBinder) msg.obj;
+ if (DEBUG_DRAG) {
+ Slog.w(TAG_WM, "Timeout starting drag by win " + win);
+ }
+ synchronized (mService.mWindowMap) {
+ // !!! TODO: ANR the app that has failed to start the drag in time
+ if (mDragState != null) {
+ mDragState.unregister();
+ mDragState.reset();
+ mDragState = null;
+ }
+ }
+ break;
+ }
+
+ case MSG_DRAG_END_TIMEOUT: {
+ IBinder win = (IBinder) msg.obj;
+ if (DEBUG_DRAG) {
+ Slog.w(TAG_WM, "Timeout ending drag to win " + win);
+ }
+ synchronized (mService.mWindowMap) {
+ // !!! TODO: ANR the drag-receiving app
+ if (mDragState != null) {
+ mDragState.mDragResult = false;
+ mDragState.endDragLocked();
+ }
+ }
+ break;
+ }
+
+ case MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT: {
+ if (DEBUG_DRAG)
+ Slog.d(TAG_WM, "Drag ending; tearing down input channel");
+ DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj;
+ if (interceptor != null) {
+ synchronized (mService.mWindowMap) {
+ interceptor.tearDown();
+ }
+ }
+ break;
+ }
+
+ case MSG_ANIMATION_END: {
+ synchronized (mService.mWindowMap) {
+ if (mDragState == null) {
+ Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " +
+ "plyaing animation");
+ return;
+ }
+ mDragState.onAnimationEndLocked();
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DragInputEventReceiver.java b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
new file mode 100644
index 0000000..b4bbc90
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.MotionEvent.BUTTON_STYLUS_PRIMARY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.os.Looper;
+import android.util.Slog;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+
+/**
+ * Input receiver for drag and drop
+ */
+class DragInputEventReceiver extends InputEventReceiver {
+ private final WindowManagerService mService;
+ private final DragDropController mDragDropController;
+
+ // Set, if stylus button was down at the start of the drag.
+ private boolean mStylusButtonDownAtStart;
+ // Indicates the first event to check for button state.
+ private boolean mIsStartEvent = true;
+ // Set to true to ignore input events after the drag gesture is complete but the drag events
+ // are still being dispatched.
+ private boolean mMuteInput = false;
+
+ public DragInputEventReceiver(InputChannel inputChannel, Looper looper,
+ DragDropController controller, WindowManagerService service) {
+ super(inputChannel, looper);
+ mDragDropController = controller;
+ mService = service;
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event, int displayId) {
+ boolean handled = false;
+ try {
+ synchronized (mService.mWindowMap) {
+ if (!mDragDropController.dragDropActiveLocked()) {
+ // The drag has ended but the clean-up message has not been processed by
+ // window manager. Drop events that occur after this until window manager
+ // has a chance to clean-up the input handle.
+ handled = true;
+ return;
+ }
+ if (!(event instanceof MotionEvent)
+ || (event.getSource() & SOURCE_CLASS_POINTER) == 0
+ || mMuteInput) {
+ return;
+ }
+ final MotionEvent motionEvent = (MotionEvent) event;
+ boolean endDrag = false;
+ final float newX = motionEvent.getRawX();
+ final float newY = motionEvent.getRawY();
+ final boolean isStylusButtonDown =
+ (motionEvent.getButtonState() & BUTTON_STYLUS_PRIMARY) != 0;
+
+ if (mIsStartEvent) {
+ if (isStylusButtonDown) {
+ // First event and the button was down, check for the button being
+ // lifted in the future, if that happens we'll drop the item.
+ mStylusButtonDownAtStart = true;
+ }
+ mIsStartEvent = false;
+ }
+
+ switch (motionEvent.getAction()) {
+ case ACTION_DOWN: {
+ if (DEBUG_DRAG) Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
+ }
+ break;
+
+ case ACTION_MOVE: {
+ if (mStylusButtonDownAtStart && !isStylusButtonDown) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Button no longer pressed; dropping at "
+ + newX + "," + newY);
+ }
+ mMuteInput = true;
+ endDrag = mDragDropController.mDragState
+ .notifyDropLocked(newX, newY);
+ } else {
+ // move the surface and tell the involved window(s) where we are
+ mDragDropController.mDragState.notifyMoveLocked(newX, newY);
+ }
+ }
+ break;
+
+ case ACTION_UP: {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Got UP on move channel; dropping at "
+ + newX + "," + newY);
+ }
+ mMuteInput = true;
+ endDrag = mDragDropController.mDragState
+ .notifyDropLocked(newX, newY);
+ }
+ break;
+
+ case ACTION_CANCEL: {
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
+ mMuteInput = true;
+ endDrag = true;
+ }
+ break;
+ }
+
+ if (endDrag) {
+ if (DEBUG_DRAG)
+ Slog.d(TAG_WM, "Drag ended; tearing down state");
+ // tell all the windows that the drag has ended
+ // endDragLocked will post back to looper to dispose the receiver
+ // since we still need the receiver for the last finishInputEvent.
+ mDragDropController.mDragState.endDragLocked();
+ mStylusButtonDownAtStart = false;
+ mIsStartEvent = true;
+ }
+
+ handled = true;
+ }
+ } catch (Exception e) {
+ Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
+ } finally {
+ finishInputEvent(event, handled);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 3fdafc7..861fb44 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,21 +16,26 @@
package com.android.server.wm;
+import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
+import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT;
+import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.animation.Animator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
-import android.graphics.Matrix;
import android.graphics.Point;
import android.hardware.input.InputManager;
import android.os.Build;
import android.os.IBinder;
-import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -46,21 +51,12 @@
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.Transformation;
-import android.view.animation.TranslateAnimation;
-
-import com.android.server.input.InputApplicationHandle;
-import com.android.server.input.InputWindowHandle;
-import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
-import com.android.server.wm.WindowManagerService.H;
import com.android.internal.view.IDragAndDropPermissions;
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.input.InputWindowHandle;
import java.util.ArrayList;
@@ -78,8 +74,18 @@
View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
+ // Property names for animations
+ private static final String ANIMATED_PROPERTY_X = "x";
+ private static final String ANIMATED_PROPERTY_Y = "y";
+ private static final String ANIMATED_PROPERTY_ALPHA = "alpha";
+ private static final String ANIMATED_PROPERTY_SCALE = "scale";
+
final WindowManagerService mService;
+ final DragDropController mDragDropController;
IBinder mToken;
+ /**
+ * Do not use the variable from the out of animation thread while mAnimator is not null.
+ */
SurfaceControl mSurfaceControl;
int mFlags;
IBinder mLocalWin;
@@ -101,14 +107,14 @@
boolean mDragInProgress;
DisplayContent mDisplayContent;
- private Animation mAnimation;
- final Transformation mTransformation = new Transformation();
+ @Nullable private ValueAnimator mAnimator;
private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
private Point mDisplaySize = new Point();
DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
int flags, IBinder localWin) {
mService = service;
+ mDragDropController = service.mDragDropController;
mToken = token;
mSurfaceControl = surface;
mFlags = flags;
@@ -117,6 +123,10 @@
}
void reset() {
+ if (mAnimator != null) {
+ Slog.wtf(TAG_WM,
+ "Unexpectedly destroying mSurfaceControl while animation is running");
+ }
if (mSurfaceControl != null) {
mSurfaceControl.destroy();
}
@@ -140,8 +150,8 @@
mServerChannel = channels[0];
mClientChannel = channels[1];
mService.mInputManager.registerInputChannel(mServerChannel, null);
- mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel,
- mService.mH.getLooper());
+ mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
+ mService.mH.getLooper(), mDragDropController, mService);
mDragApplicationHandle = new InputApplicationHandle(null);
mDragApplicationHandle.name = "drag";
@@ -152,7 +162,7 @@
display.getDisplayId());
mDragWindowHandle.name = "drag";
mDragWindowHandle.inputChannel = mServerChannel;
- mDragWindowHandle.layer = getDragLayerLw();
+ mDragWindowHandle.layer = getDragLayerLocked();
mDragWindowHandle.layoutParamsFlags = 0;
mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
mDragWindowHandle.dispatchingTimeoutNanos =
@@ -231,14 +241,14 @@
Slog.e(TAG_WM, "Unregister of nonexistent drag input channel");
} else {
// Input channel should be disposed on the thread where the input is being handled.
- mService.mH.obtainMessage(
- H.TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor).sendToTarget();
+ mDragDropController.sendHandlerMessage(
+ MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor);
mInputInterceptor = null;
mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
}
}
- int getDragLayerLw() {
+ int getDragLayerLocked() {
return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_DRAG)
* WindowManagerService.TYPE_LAYER_MULTIPLIER
+ WindowManagerService.TYPE_LAYER_OFFSET;
@@ -246,7 +256,7 @@
/* call out to each visible window/session informing it about the drag
*/
- void broadcastDragStartedLw(final float touchX, final float touchY) {
+ void broadcastDragStartedLocked(final float touchX, final float touchY) {
mOriginalX = mCurrentX = touchX;
mOriginalY = mCurrentY = touchY;
@@ -273,7 +283,7 @@
}
mDisplayContent.forAllWindows(w -> {
- sendDragStartedLw(w, touchX, touchY, mDataDescription);
+ sendDragStartedLocked(w, touchX, touchY, mDataDescription);
}, false /* traverseTopToBottom */ );
}
@@ -285,7 +295,7 @@
* This method clones the 'event' parameter if it's being delivered to the same
* process, so it's safe for the caller to call recycle() on the event afterwards.
*/
- private void sendDragStartedLw(WindowState newWin, float touchX, float touchY,
+ private void sendDragStartedLocked(WindowState newWin, float touchX, float touchY,
ClipDescription desc) {
if (mDragInProgress && isValidDropTarget(newWin)) {
DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
@@ -334,7 +344,7 @@
* previously been notified, i.e. it became visible after the drag operation
* was begun. This is a rare case.
*/
- void sendDragStartedIfNeededLw(WindowState newWin) {
+ void sendDragStartedIfNeededLocked(WindowState newWin) {
if (mDragInProgress) {
// If we have sent the drag-started, we needn't do so again
if (isWindowNotified(newWin)) {
@@ -343,7 +353,7 @@
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
}
- sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
+ sendDragStartedLocked(newWin, mCurrentX, mCurrentY, mDataDescription);
}
}
@@ -356,7 +366,7 @@
return false;
}
- private void broadcastDragEndedLw() {
+ private void broadcastDragEndedLocked() {
final int myPid = Process.myPid();
if (DEBUG_DRAG) {
@@ -387,28 +397,38 @@
mDragInProgress = false;
}
- void endDragLw() {
- if (mAnimation != null) {
+ void endDragLocked() {
+ if (mAnimator != null) {
return;
}
if (!mDragResult) {
- mAnimation = createReturnAnimationLocked();
- mService.scheduleAnimationLocked();
+ mAnimator = createReturnAnimationLocked();
return; // Will call cleanUpDragLw when the animation is done.
}
- cleanUpDragLw();
+ cleanUpDragLocked();
}
- void cancelDragLw() {
- if (mAnimation != null) {
+ void cancelDragLocked() {
+ if (mAnimator != null) {
return;
}
- mAnimation = createCancelAnimationLocked();
- mService.scheduleAnimationLocked();
+ if (!mDragInProgress) {
+ // This can happen if an app invokes Session#cancelDragAndDrop before
+ // Session#performDrag. Reset the drag state:
+ // 1. without sending the end broadcast because the start broadcast has not been sent,
+ // and
+ // 2. without playing the cancel animation because H.DRAG_START_TIMEOUT may be sent to
+ // WindowManagerService, which will cause DragState#reset() while playing the
+ // cancel animation.
+ reset();
+ mDragDropController.mDragState = null;
+ return;
+ }
+ mAnimator = createCancelAnimationLocked();
}
- private void cleanUpDragLw() {
- broadcastDragEndedLw();
+ private void cleanUpDragLocked() {
+ broadcastDragEndedLocked();
if (isFromSource(InputDevice.SOURCE_MOUSE)) {
mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
}
@@ -418,11 +438,11 @@
// free our resources and drop all the object references
reset();
- mService.mDragState = null;
+ mDragDropController.mDragState = null;
}
- void notifyMoveLw(float x, float y) {
- if (mAnimation != null) {
+ void notifyMoveLocked(float x, float y) {
+ if (mAnimator != null) {
return;
}
mCurrentX = x;
@@ -430,7 +450,7 @@
// Move the surface to the given touch
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, ">>> OPEN TRANSACTION notifyMoveLw");
+ TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
mService.openSurfaceTransaction();
try {
mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
@@ -438,14 +458,14 @@
+ mSurfaceControl + ": pos=(" +
(int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("notifyMoveLw");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLw");
+ TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked");
}
- notifyLocationLw(x, y);
+ notifyLocationLocked(x, y);
}
- void notifyLocationLw(float x, float y) {
+ void notifyLocationLocked(float x, float y) {
// Tell the affected window
WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
if (touchedWin != null && !isWindowNotified(touchedWin)) {
@@ -490,8 +510,8 @@
// Find the drop target and tell it about the data. Returns 'true' if we can immediately
// dispatch the global drag-ended message, 'false' if we need to wait for a
// result from the recipient.
- boolean notifyDropLw(float x, float y) {
- if (mAnimation != null) {
+ boolean notifyDropLocked(float x, float y) {
+ if (mAnimator != null) {
return false;
}
mCurrentX = x;
@@ -534,9 +554,7 @@
touchedWin.mClient.dispatchDragEvent(evt);
// 5 second timeout for this window to respond to the drop
- mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token);
- Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
- mService.mH.sendMessageDelayed(msg, 5000);
+ mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, token);
} catch (RemoteException e) {
Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
return true;
@@ -549,6 +567,15 @@
return false;
}
+ void onAnimationEndLocked() {
+ if (mAnimator == null) {
+ Slog.wtf(TAG_WM, "Unexpected null mAnimator");
+ return;
+ }
+ mAnimator = null;
+ cleanUpDragLocked();
+ }
+
private static DragEvent obtainDragEvent(WindowState win, int action,
float x, float y, Object localState,
ClipDescription description, ClipData data,
@@ -560,66 +587,99 @@
dragAndDropPermissions, result);
}
- boolean stepAnimationLocked(long currentTimeMs) {
- if (mAnimation == null) {
- return false;
- }
+ private ValueAnimator createReturnAnimationLocked() {
+ final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX,
+ mOriginalX - mThumbOffsetX),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY,
+ mOriginalY - mThumbOffsetY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 1),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2));
- mTransformation.clear();
- if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) {
- cleanUpDragLw();
- return false;
- }
-
- mTransformation.getMatrix().postTranslate(
- mCurrentX - mThumbOffsetX, mCurrentY - mThumbOffsetY);
- final float tmpFloats[] = mService.mTmpFloats;
- mTransformation.getMatrix().getValues(tmpFloats);
- mSurfaceControl.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
- mSurfaceControl.setAlpha(mTransformation.getAlpha());
- mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
- tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
- return true;
- }
-
- private Animation createReturnAnimationLocked() {
- final AnimationSet set = new AnimationSet(false);
final float translateX = mOriginalX - mCurrentX;
final float translateY = mOriginalY - mCurrentY;
- set.addAnimation(new TranslateAnimation( 0, translateX, 0, translateY));
- set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2));
// Adjust the duration to the travel distance.
final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY);
final double displayDiagonal =
Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
* (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
- set.setDuration(duration);
- set.setInterpolator(mCubicEaseOutInterpolator);
- set.initialize(0, 0, 0, 0);
- set.start(); // Will start on the first call to getTransformation.
- return set;
+ final AnimationListener listener = new AnimationListener();
+ animator.setDuration(duration);
+ animator.setInterpolator(mCubicEaseOutInterpolator);
+ animator.addListener(listener);
+ animator.addUpdateListener(listener);
+
+ mService.mAnimationHandler.post(() -> animator.start());
+ return animator;
}
- private Animation createCancelAnimationLocked() {
- final AnimationSet set = new AnimationSet(false);
- set.addAnimation(new ScaleAnimation(1, 0, 1, 0, mThumbOffsetX, mThumbOffsetY));
- set.addAnimation(new AlphaAnimation(mOriginalAlpha, 0));
- set.setDuration(MIN_ANIMATION_DURATION_MS);
- set.setInterpolator(mCubicEaseOutInterpolator);
- set.initialize(0, 0, 0, 0);
- set.start(); // Will start on the first call to getTransformation.
- return set;
+ private ValueAnimator createCancelAnimationLocked() {
+ final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 0),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
+ final AnimationListener listener = new AnimationListener();
+ animator.setDuration(MIN_ANIMATION_DURATION_MS);
+ animator.setInterpolator(mCubicEaseOutInterpolator);
+ animator.addListener(listener);
+ animator.addUpdateListener(listener);
+
+ mService.mAnimationHandler.post(() -> animator.start());
+ return animator;
}
private boolean isFromSource(int source) {
return (mTouchSource & source) == source;
}
- void overridePointerIconLw(int touchSource) {
+ void overridePointerIconLocked(int touchSource) {
mTouchSource = touchSource;
if (isFromSource(InputDevice.SOURCE_MOUSE)) {
InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
}
}
+
+ private class AnimationListener
+ implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
+ transaction.setPosition(
+ mSurfaceControl,
+ (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X),
+ (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y));
+ transaction.setAlpha(
+ mSurfaceControl,
+ (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
+ transaction.setMatrix(
+ mSurfaceControl,
+ (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
+ 0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
+ transaction.apply();
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animator) {}
+
+ @Override
+ public void onAnimationCancel(Animator animator) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {}
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ // Updating mDragState requires the WM lock so continues it on the out of
+ // AnimationThread.
+ mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
index 19bd8e9..8bec8d7 100644
--- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -56,8 +56,11 @@
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x,
- mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+ ctrl = new SurfaceControl.Builder(session)
+ .setName("EmulatorDisplayOverlay")
+ .setSize(mScreenSize.x, mScreenSize.y)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
ctrl.setLayerStack(display.getLayerStack());
ctrl.setLayer(zOrder);
ctrl.setPosition(0, 0);
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 36753b7..d40db8c 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -16,21 +16,36 @@
package com.android.server.wm;
+import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
import android.view.Display;
import android.view.InputChannel;
import android.view.WindowManager;
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputWindowHandle;
-class InputConsumerImpl {
+import java.io.PrintWriter;
+
+class InputConsumerImpl implements IBinder.DeathRecipient {
final WindowManagerService mService;
final InputChannel mServerChannel, mClientChannel;
final InputApplicationHandle mApplicationHandle;
final InputWindowHandle mWindowHandle;
- InputConsumerImpl(WindowManagerService service, String name, InputChannel inputChannel) {
+ final IBinder mToken;
+ final String mName;
+ final int mClientPid;
+ final UserHandle mClientUser;
+
+ InputConsumerImpl(WindowManagerService service, IBinder token, String name,
+ InputChannel inputChannel, int clientPid, UserHandle clientUser) {
mService = service;
+ mToken = token;
+ mName = name;
+ mClientPid = clientPid;
+ mClientUser = clientUser;
InputChannel[] channels = InputChannel.openInputChannelPair(name);
mServerChannel = channels[0];
@@ -68,6 +83,26 @@
mWindowHandle.scaleFactor = 1.0f;
}
+ void linkToDeathRecipient() {
+ if (mToken == null) {
+ return;
+ }
+
+ try {
+ mToken.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ // Client died, do nothing
+ }
+ }
+
+ void unlinkFromDeathRecipient() {
+ if (mToken == null) {
+ return;
+ }
+
+ mToken.unlinkToDeath(this, 0);
+ }
+
void layout(int dw, int dh) {
mWindowHandle.touchableRegion.set(0, 0, dw, dh);
mWindowHandle.frameLeft = 0;
@@ -86,5 +121,19 @@
mService.mInputManager.unregisterInputChannel(mServerChannel);
mClientChannel.dispose();
mServerChannel.dispose();
+ unlinkFromDeathRecipient();
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mService.getWindowManagerLock()) {
+ // Clean up the input consumer
+ mService.mInputMonitor.destroyInputConsumer(mName);
+ unlinkFromDeathRecipient();
+ }
+ }
+
+ void dump(PrintWriter pw, String name, String prefix) {
+ pw.println(prefix + " name=" + name + " pid=" + mClientPid + " user=" + mClientUser);
}
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 238cb9f..40eab45 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -25,6 +25,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
@@ -34,8 +35,11 @@
import android.app.ActivityManager;
import android.graphics.Rect;
import android.os.Debug;
+import android.os.IBinder;
import android.os.Looper;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -43,7 +47,6 @@
import android.view.InputEventReceiver;
import android.view.KeyEvent;
import android.view.WindowManager;
-
import android.view.WindowManagerPolicy;
import com.android.server.input.InputApplicationHandle;
@@ -106,8 +109,9 @@
EventReceiverInputConsumer(WindowManagerService service, InputMonitor monitor,
Looper looper, String name,
- InputEventReceiver.Factory inputEventReceiverFactory) {
- super(service, name, null);
+ InputEventReceiver.Factory inputEventReceiverFactory,
+ int clientPid, UserHandle clientUser) {
+ super(service, null /* token */, name, null /* inputChannel */, clientPid, clientUser);
mInputMonitor = monitor;
mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
mClientChannel, looper);
@@ -129,6 +133,7 @@
private void addInputConsumer(String name, InputConsumerImpl consumer) {
mInputConsumers.put(name, consumer);
+ consumer.linkToDeathRecipient();
updateInputWindowsLw(true /* force */);
}
@@ -166,17 +171,20 @@
}
final EventReceiverInputConsumer consumer = new EventReceiverInputConsumer(mService,
- this, looper, name, inputEventReceiverFactory);
+ this, looper, name, inputEventReceiverFactory, Process.myPid(),
+ UserHandle.SYSTEM);
addInputConsumer(name, consumer);
return consumer;
}
- void createInputConsumer(String name, InputChannel inputChannel) {
+ void createInputConsumer(IBinder token, String name, InputChannel inputChannel, int clientPid,
+ UserHandle clientUser) {
if (mInputConsumers.containsKey(name)) {
throw new IllegalStateException("Existing input consumer found with name: " + name);
}
- final InputConsumerImpl consumer = new InputConsumerImpl(mService, name, inputChannel);
+ final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name,
+ inputChannel, clientPid, clientUser);
switch (name) {
case INPUT_CONSUMER_WALLPAPER:
consumer.mWindowHandle.hasWallpaper = true;
@@ -367,12 +375,13 @@
// currently has touch focus.
// If there's a drag in flight, provide a pseudo-window to catch drag input
- final boolean inDrag = (mService.mDragState != null);
+ final boolean inDrag = mService.mDragDropController.dragDropActiveLocked();
if (inDrag) {
if (DEBUG_DRAG) {
Log.d(TAG_WM, "Inserting drag window");
}
- final InputWindowHandle dragWindowHandle = mService.mDragState.getInputWindowHandle();
+ final InputWindowHandle dragWindowHandle =
+ mService.mDragDropController.mDragState.getInputWindowHandle();
if (dragWindowHandle != null) {
addInputWindowHandle(dragWindowHandle);
} else {
@@ -592,7 +601,7 @@
if (!inputConsumerKeys.isEmpty()) {
pw.println(prefix + "InputConsumers:");
for (String key : inputConsumerKeys) {
- pw.println(prefix + " name=" + key);
+ mInputConsumers.get(key).dump(pw, key, prefix);
}
}
}
@@ -689,7 +698,7 @@
// If there's a drag in progress and 'child' is a potential drop target,
// make sure it's been told about the drag
if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
- mService.mDragState.sendDragStartedIfNeededLw(w);
+ mService.mDragDropController.mDragState.sendDragStartedIfNeededLocked(w);
}
addInputWindowHandle(
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index fd57470..bcb6e673 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -457,7 +457,7 @@
try {
forAllWindows(sRemoveReplacedWindowsConsumer, true /* traverseTopToBottom */);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("removeReplacedWindows");
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION removeReplacedWindows");
}
}
@@ -599,7 +599,7 @@
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
}
@@ -1081,19 +1081,21 @@
@CallSuper
@Override
- public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
final long token = proto.start(fieldId);
- super.writeToProto(proto, WINDOW_CONTAINER);
+ super.writeToProto(proto, WINDOW_CONTAINER, trim);
if (mService.mDisplayReady) {
final int count = mChildren.size();
for (int i = 0; i < count; ++i) {
final DisplayContent displayContent = mChildren.get(i);
- displayContent.writeToProto(proto, DISPLAYS);
+ displayContent.writeToProto(proto, DISPLAYS, trim);
}
}
- forAllWindows((w) -> {
- w.writeIdentifierToProto(proto, WINDOWS);
- }, true);
+ if (!trim) {
+ forAllWindows((w) -> {
+ w.writeIdentifierToProto(proto, WINDOWS);
+ }, true);
+ }
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 8e99be8..3350fea 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -269,14 +269,11 @@
try {
try {
- int flags = SurfaceControl.HIDDEN;
- if (isSecure) {
- flags |= SurfaceControl.SECURE;
- }
-
- mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
- mWidth, mHeight,
- PixelFormat.OPAQUE, flags);
+ mSurfaceControl = new SurfaceControl.Builder(session)
+ .setName("ScreenshotSurface")
+ .setSize(mWidth, mHeight)
+ .setSecure(isSecure)
+ .build();
// capture a screenshot into the surface we just created
Surface sur = new Surface();
@@ -299,7 +296,7 @@
setRotationInTransaction(originalRotation);
} finally {
if (!inTransaction) {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation");
}
@@ -570,7 +567,7 @@
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -610,7 +607,7 @@
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -632,7 +629,7 @@
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
TAG_WM,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 4dd147e..ad7c300 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -68,9 +68,7 @@
* This class represents an active client session. There is generally one
* Session object per process that is interacting with the window manager.
*/
-// Needs to be public and not final so we can mock during tests...sucks I know :(
-public class Session extends IWindowSession.Stub
- implements IBinder.DeathRecipient {
+class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
final WindowManagerService mService;
final IWindowSessionCallback mCallback;
final IInputMethodClient mClient;
@@ -83,6 +81,7 @@
private final Set<WindowSurfaceController> mAppOverlaySurfaces = new HashSet<>();
// Set of visible alert window surfaces connected to this session.
private final Set<WindowSurfaceController> mAlertWindowSurfaces = new HashSet<>();
+ private final DragDropController mDragDropController;
final boolean mCanAddInternalSystemWindow;
final boolean mCanHideNonSystemOverlayWindows;
final boolean mCanAcquireSleepToken;
@@ -108,6 +107,7 @@
mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER)
== PERMISSION_GRANTED;
mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
+ mDragDropController = mService.mDragDropController;
StringBuilder sb = new StringBuilder();
sb.append("Session{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
@@ -306,94 +306,56 @@
/* Drag/drop */
@Override
- public IBinder prepareDrag(IWindow window, int flags,
- int width, int height, Surface outSurface) {
- return mService.prepareDragSurface(window, mSurfaceSession, flags,
- width, height, outSurface);
+ public IBinder prepareDrag(IWindow window, int flags, int width, int height,
+ Surface outSurface) {
+ final int callerPid = Binder.getCallingPid();
+ final int callerUid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mDragDropController.prepareDrag(
+ mSurfaceSession, callerPid, callerUid, window, flags, width, height,
+ outSurface);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
@Override
public boolean performDrag(IWindow window, IBinder dragToken,
int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
ClipData data) {
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
+ return mDragDropController.performDrag(window, dragToken, touchSource,
+ touchX, touchY, thumbCenterX, thumbCenterY, data);
+ }
+
+ @Override
+ public void reportDropResult(IWindow window, boolean consumed) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mDragDropController.reportDropResult(window, consumed);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
+ }
- synchronized (mService.mWindowMap) {
- if (mService.mDragState == null) {
- Slog.w(TAG_WM, "No drag prepared");
- throw new IllegalStateException("performDrag() without prepareDrag()");
- }
-
- if (dragToken != mService.mDragState.mToken) {
- Slog.w(TAG_WM, "Performing mismatched drag");
- throw new IllegalStateException("performDrag() does not match prepareDrag()");
- }
-
- WindowState callingWin = mService.windowForClientLocked(null, window, false);
- if (callingWin == null) {
- Slog.w(TAG_WM, "Bad requesting window " + window);
- return false; // !!! TODO: throw here?
- }
-
- // !!! TODO: if input is not still focused on the initiating window, fail
- // the drag initiation (e.g. an alarm window popped up just as the application
- // called performDrag()
-
- mService.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
-
- // !!! TODO: extract the current touch (x, y) in screen coordinates. That
- // will let us eliminate the (touchX,touchY) parameters from the API.
-
- // !!! FIXME: put all this heavy stuff onto the mH looper, as well as
- // the actual drag event dispatch stuff in the dragstate
-
- final DisplayContent displayContent = callingWin.getDisplayContent();
- if (displayContent == null) {
- return false;
- }
- Display display = displayContent.getDisplay();
- mService.mDragState.register(display);
- if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
- mService.mDragState.getInputChannel())) {
- Slog.e(TAG_WM, "Unable to transfer touch focus");
- mService.mDragState.unregister();
- mService.mDragState.reset();
- mService.mDragState = null;
- return false;
- }
-
- mService.mDragState.mDisplayContent = displayContent;
- mService.mDragState.mData = data;
- mService.mDragState.broadcastDragStartedLw(touchX, touchY);
- mService.mDragState.overridePointerIconLw(touchSource);
-
- // remember the thumb offsets for later
- mService.mDragState.mThumbOffsetX = thumbCenterX;
- mService.mDragState.mThumbOffsetY = thumbCenterY;
-
- // Make the surface visible at the proper location
- final SurfaceControl surfaceControl = mService.mDragState.mSurfaceControl;
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, ">>> OPEN TRANSACTION performDrag");
- mService.openSurfaceTransaction();
- try {
- surfaceControl.setPosition(touchX - thumbCenterX,
- touchY - thumbCenterY);
- surfaceControl.setLayer(mService.mDragState.getDragLayerLw());
- surfaceControl.setLayerStack(display.getLayerStack());
- surfaceControl.show();
- } finally {
- mService.closeSurfaceTransaction();
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, "<<< CLOSE TRANSACTION performDrag");
- }
-
- mService.mDragState.notifyLocationLw(touchX, touchY);
+ @Override
+ public void cancelDragAndDrop(IBinder dragToken) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mDragDropController.cancelDragAndDrop(dragToken);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
+ }
- return true; // success!
+ @Override
+ public void dragRecipientEntered(IWindow window) {
+ mDragDropController.dragRecipientEntered(window);
+ }
+
+ @Override
+ public void dragRecipientExited(IWindow window) {
+ mDragDropController.dragRecipientExited(window);
}
@Override
@@ -410,90 +372,6 @@
}
@Override
- public void reportDropResult(IWindow window, boolean consumed) {
- IBinder token = window.asBinder();
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
- }
-
- synchronized (mService.mWindowMap) {
- long ident = Binder.clearCallingIdentity();
- try {
- if (mService.mDragState == null) {
- // Most likely the drop recipient ANRed and we ended the drag
- // out from under it. Log the issue and move on.
- Slog.w(TAG_WM, "Drop result given but no drag in progress");
- return;
- }
-
- if (mService.mDragState.mToken != token) {
- // We're in a drag, but the wrong window has responded.
- Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
- throw new IllegalStateException("reportDropResult() by non-recipient");
- }
-
- // The right window has responded, even if it's no longer around,
- // so be sure to halt the timeout even if the later WindowState
- // lookup fails.
- mService.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder());
- WindowState callingWin = mService.windowForClientLocked(null, window, false);
- if (callingWin == null) {
- Slog.w(TAG_WM, "Bad result-reporting window " + window);
- return; // !!! TODO: throw here?
- }
-
- mService.mDragState.mDragResult = consumed;
- mService.mDragState.endDragLw();
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @Override
- public void cancelDragAndDrop(IBinder dragToken) {
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "cancelDragAndDrop");
- }
-
- synchronized (mService.mWindowMap) {
- long ident = Binder.clearCallingIdentity();
- try {
- if (mService.mDragState == null) {
- Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
- throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
- }
-
- if (mService.mDragState.mToken != dragToken) {
- Slog.w(TAG_WM,
- "cancelDragAndDrop() does not match prepareDrag()");
- throw new IllegalStateException(
- "cancelDragAndDrop() does not match prepareDrag()");
- }
-
- mService.mDragState.mDragResult = false;
- mService.mDragState.cancelDragLw();
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @Override
- public void dragRecipientEntered(IWindow window) {
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
- }
- }
-
- @Override
- public void dragRecipientExited(IWindow window) {
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
- }
- }
-
- @Override
public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
synchronized(mService.mWindowMap) {
long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index 1fda832..aff1bc6 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -152,7 +152,8 @@
}
}
- public void positionChildAtBottom(TaskWindowContainerController child) {
+ public void positionChildAtBottom(TaskWindowContainerController child,
+ boolean includingParents) {
if (child == null) {
// TODO: Fix the call-points that cause this to happen.
return;
@@ -164,7 +165,7 @@
Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
return;
}
- mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */);
+ mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents);
if (mService.mAppTransition.isTransitionSet()) {
childTask.setSendingToBottom(true);
diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java
index d1547eb..eb8ee69 100644
--- a/services/core/java/com/android/server/wm/StrictModeFlash.java
+++ b/services/core/java/com/android/server/wm/StrictModeFlash.java
@@ -44,8 +44,11 @@
public StrictModeFlash(Display display, SurfaceSession session) {
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl(session, "StrictModeFlash",
- 1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+ ctrl = new SurfaceControl.Builder(session)
+ .setName("StrictModeFlash")
+ .setSize(1, 1)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
ctrl.setLayerStack(display.getLayerStack());
ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary.
ctrl.setPosition(0, 0);
diff --git a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java b/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
index b0eaf14..a5080d5 100644
--- a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
+++ b/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
@@ -66,10 +66,10 @@
mWindowSurfaceController = other.mWindowSurfaceController;
}
- public SurfaceControlWithBackground(SurfaceSession s, String name, int w, int h, int format,
- int flags, int windowType, int ownerUid,
+ public SurfaceControlWithBackground(String name, SurfaceControl.Builder b,
+ int windowType, int w, int h,
WindowSurfaceController windowSurfaceController) throws OutOfResourcesException {
- super(s, name, w, h, format, flags, windowType, ownerUid);
+ super(b.build());
// We should only show background behind app windows that are letterboxed in a task.
if ((windowType != TYPE_BASE_APPLICATION && windowType != TYPE_APPLICATION_STARTING)
@@ -80,9 +80,11 @@
mLastWidth = w;
mLastHeight = h;
mWindowSurfaceController.getContainerRect(mTmpContainerRect);
- mBackgroundControl = new SurfaceControl(s, "Background for - " + name,
- mTmpContainerRect.width(), mTmpContainerRect.height(), PixelFormat.OPAQUE,
- flags | SurfaceControl.FX_SURFACE_DIM);
+ mBackgroundControl = b.setName("Background for - " + name)
+ .setSize(mTmpContainerRect.width(), mTmpContainerRect.height())
+ .setFormat(OPAQUE)
+ .setColorLayer(true)
+ .build();
}
@Override
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 891d637a..13435d7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -419,7 +419,7 @@
return mFillsParent
|| !inSplitScreenSecondaryWindowingMode()
|| displayContent == null
- || displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null;
+ || displayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null;
}
/** Original bounds of the task if applicable, otherwise fullscreen rect. */
@@ -678,7 +678,7 @@
mChildren.get(i).forceWindowsScaleableInTransaction(force);
}
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("forceWindowsScaleable");
}
}
@@ -725,13 +725,13 @@
@CallSuper
@Override
- public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
final long token = proto.start(fieldId);
- super.writeToProto(proto, WINDOW_CONTAINER);
+ super.writeToProto(proto, WINDOW_CONTAINER, trim);
proto.write(ID, mTaskId);
for (int i = mChildren.size() - 1; i >= 0; i--) {
final AppWindowToken appWindowToken = mChildren.get(i);
- appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS);
+ appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS, trim);
}
proto.write(FILLS_PARENT, mFillsParent);
mBounds.writeToProto(proto, BOUNDS);
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 87de151..12f6b5a 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -16,8 +16,8 @@
package com.android.server.wm;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.RESIZE_MODE_USER;
import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -210,9 +210,9 @@
if (mCurrentDimSide != CTRL_NONE) {
final int createMode = mCurrentDimSide == CTRL_LEFT
- ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
- : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
- mService.mActivityManager.moveTaskToDockedStack(
+ ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+ : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+ mService.mActivityManager.setTaskWindowingModeSplitScreenPrimary(
mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
null /* initialBounds */);
}
@@ -637,7 +637,7 @@
} else {
showDimLayer();
}
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("updateDimLayerVisibility");
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 4698d72..3ce090a 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -304,9 +304,11 @@
final SurfaceSession session = new SurfaceSession(mSurface);
// Keep a reference to it such that it doesn't get destroyed when finalized.
- mChildSurfaceControl = new SurfaceControl(session,
- mTitle + " - task-snapshot-surface",
- buffer.getWidth(), buffer.getHeight(), buffer.getFormat(), HIDDEN);
+ mChildSurfaceControl = new SurfaceControl.Builder(session)
+ .setName(mTitle + " - task-snapshot-surface")
+ .setSize(buffer.getWidth(), buffer.getHeight())
+ .setFormat(buffer.getFormat())
+ .build();
Surface surface = new Surface();
surface.copyFrom(mChildSurfaceControl);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index d170b6f..6e89e3e 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -16,8 +16,8 @@
package com.android.server.wm;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -294,7 +294,7 @@
if (mFillsParent
|| !inSplitScreenSecondaryWindowingMode()
|| mDisplayContent == null
- || mDisplayContent.getSplitScreenPrimaryStackStack() != null) {
+ || mDisplayContent.getSplitScreenPrimaryStack() != null) {
return true;
}
return false;
@@ -450,8 +450,8 @@
// might change after a rotation and the original values will be invalid.
mService.setDockedStackCreateStateLocked(
(newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
- ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
- : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
+ ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+ : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT,
null);
mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
}
@@ -523,7 +523,7 @@
final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
mService.mContext.getResources(), displayWidth, displayHeight,
dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds,
- isMinimizedDockAndHomeStackResizable());
+ getDockSide(), isMinimizedDockAndHomeStackResizable());
final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition);
// Recalculate the bounds based on the position of the target.
@@ -693,7 +693,7 @@
"animation background stackId=" + mStackId);
Rect bounds = null;
- final TaskStack dockedStack = dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ final TaskStack dockedStack = dc.getSplitScreenPrimaryStackIgnoringVisibility();
if (inSplitScreenPrimaryWindowingMode()
|| (dockedStack != null && inSplitScreenSecondaryWindowingMode()
&& !dockedStack.fillsParent())) {
@@ -708,7 +708,7 @@
dockedStack.getRawBounds(mTmpRect2);
}
final boolean dockedOnTopOrLeft = mService.mDockedStackCreateMode
- == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
getStackDockedModeBounds(mTmpRect, bounds, mTmpRect2,
mDisplayContent.mDividerControllerLocked.getContentWidth(),
dockedOnTopOrLeft);
@@ -773,7 +773,7 @@
}
final TaskStack dockedStack =
- mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
if (dockedStack == null) {
// Not sure why you are calling this method when there is no docked stack...
throw new IllegalStateException(
@@ -1008,10 +1008,13 @@
void resetAdjustedForIme(boolean adjustBoundsNow) {
if (adjustBoundsNow) {
mImeWin = null;
- mAdjustedForIme = false;
mImeGoingAway = false;
mAdjustImeAmount = 0f;
mAdjustDividerAmount = 0f;
+ if (!mAdjustedForIme) {
+ return;
+ }
+ mAdjustedForIme = false;
updateAdjustedBounds();
mService.setResizeDimLayer(false, getWindowingMode(), 1.0f);
} else {
@@ -1229,12 +1232,12 @@
@CallSuper
@Override
- public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
final long token = proto.start(fieldId);
- super.writeToProto(proto, WINDOW_CONTAINER);
+ super.writeToProto(proto, WINDOW_CONTAINER, trim);
proto.write(ID, mStackId);
for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
- mChildren.get(taskNdx).writeToProto(proto, TASKS);
+ mChildren.get(taskNdx).writeToProto(proto, TASKS, trim);
}
proto.write(FILLS_PARENT, mFillsParent);
mBounds.writeToProto(proto, BOUNDS);
diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java
index 171e575..d97aaac 100644
--- a/services/core/java/com/android/server/wm/Watermark.java
+++ b/services/core/java/com/android/server/wm/Watermark.java
@@ -114,8 +114,11 @@
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl(session, "WatermarkSurface",
- 1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+ ctrl = new SurfaceControl.Builder(session)
+ .setName("WatermarkSurface")
+ .setSize(1, 1)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
ctrl.setLayerStack(mDisplay.getLayerStack());
ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100);
ctrl.setPosition(0, 0);
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index c01ee31..1912095 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -223,10 +223,6 @@
}
}
- if (mService.mDragState != null) {
- mAnimating |= mService.mDragState.stepAnimationLocked(mCurrentTime);
- }
-
if (!mAnimating) {
cancelAnimation();
}
@@ -237,7 +233,7 @@
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("WindowAnimator");
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 563eb9c..8f4b897 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -677,17 +677,18 @@
* Write to a protocol buffer output stream. Protocol buffer message definition is at
* {@link com.android.server.wm.proto.WindowContainerProto}.
*
- * @param protoOutputStream Stream to write the WindowContainer object to.
- * @param fieldId Field Id of the WindowContainer as defined in the parent message.
+ * @param proto Stream to write the WindowContainer object to.
+ * @param fieldId Field Id of the WindowContainer as defined in the parent message.
+ * @param trim If true, reduce the amount of data written.
* @hide
*/
@CallSuper
@Override
- public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
- final long token = protoOutputStream.start(fieldId);
- super.writeToProto(protoOutputStream, CONFIGURATION_CONTAINER);
- protoOutputStream.write(ORIENTATION, mOrientation);
- protoOutputStream.end(token);
+ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
+ final long token = proto.start(fieldId);
+ super.writeToProto(proto, CONFIGURATION_CONTAINER, trim);
+ proto.write(ORIENTATION, mOrientation);
+ proto.end(token);
}
private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer<WindowState> consumer) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f0da474..bf5f4bc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -20,7 +20,7 @@
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.StatusBarManager.DISABLE_MASK;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
@@ -80,7 +80,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
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_INPUT_METHOD;
@@ -137,7 +136,6 @@
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
import android.graphics.Matrix;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -163,7 +161,9 @@
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -203,14 +203,12 @@
import android.view.IWindowSessionCallback;
import android.view.InputChannel;
import android.view.InputDevice;
-import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
@@ -226,7 +224,7 @@
import android.view.inputmethod.InputMethodManagerInternal;
import com.android.internal.R;
-import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.app.IAssistDataReceiver;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.os.IResultReceiver;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -357,8 +355,6 @@
// trying to apply a new one.
private static final boolean ALWAYS_KEEP_CURRENT = true;
- private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
-
// Enums for animation scale update types.
@Retention(RetentionPolicy.SOURCE)
@IntDef({WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE, ANIMATION_DURATION_SCALE})
@@ -367,6 +363,8 @@
private static final int TRANSITION_ANIMATION_SCALE = 1;
private static final int ANIMATION_DURATION_SCALE = 2;
+ final WindowTracing mWindowTracing;
+
final private KeyguardDisableHandler mKeyguardDisableHandler;
boolean mKeyguardGoingAway;
// VR Vr2d Display Id.
@@ -394,13 +392,14 @@
private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
@Override
- public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
- doDump(fd, pw, new String[] {"-a"});
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean asProto) {
+ doDump(fd, pw, new String[] {"-a"}, asProto);
}
@Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- doDump(fd, pw, args);
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ doDump(fd, pw, args, asProto);
}
};
@@ -561,7 +560,7 @@
// The root of the device window hierarchy.
RootWindowContainer mRoot;
- int mDockedStackCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
Rect mDockedStackCreateBounds;
private final SparseIntArray mTmpTaskIds = new SparseIntArray();
@@ -752,7 +751,7 @@
boolean mAllowTheaterModeWakeFromLayout;
TaskPositioner mTaskPositioner;
- DragState mDragState = null;
+ final DragDropController mDragDropController;
// For frozen screen animations.
private int mExitAnimId, mEnterAnimId;
@@ -772,110 +771,6 @@
private WindowContentFrameStats mTempWindowRenderStats;
- final class DragInputEventReceiver extends InputEventReceiver {
- // Set, if stylus button was down at the start of the drag.
- private boolean mStylusButtonDownAtStart;
- // Indicates the first event to check for button state.
- private boolean mIsStartEvent = true;
- // Set to true to ignore input events after the drag gesture is complete but the drag events
- // are still being dispatched.
- private boolean mMuteInput = false;
-
- public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
- super(inputChannel, looper);
- }
-
- @Override
- public void onInputEvent(InputEvent event, int displayId) {
- boolean handled = false;
- try {
- if (mDragState == null) {
- // The drag has ended but the clean-up message has not been processed by
- // window manager. Drop events that occur after this until window manager
- // has a chance to clean-up the input handle.
- handled = true;
- return;
- }
- if (event instanceof MotionEvent
- && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0
- && !mMuteInput) {
- final MotionEvent motionEvent = (MotionEvent)event;
- boolean endDrag = false;
- final float newX = motionEvent.getRawX();
- final float newY = motionEvent.getRawY();
- final boolean isStylusButtonDown =
- (motionEvent.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
-
- if (mIsStartEvent) {
- if (isStylusButtonDown) {
- // First event and the button was down, check for the button being
- // lifted in the future, if that happens we'll drop the item.
- mStylusButtonDownAtStart = true;
- }
- mIsStartEvent = false;
- }
-
- switch (motionEvent.getAction()) {
- case MotionEvent.ACTION_DOWN: {
- if (DEBUG_DRAG) {
- Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
- }
- } break;
-
- case MotionEvent.ACTION_MOVE: {
- if (mStylusButtonDownAtStart && !isStylusButtonDown) {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "Button no longer pressed; dropping at "
- + newX + "," + newY);
- mMuteInput = true;
- synchronized (mWindowMap) {
- endDrag = mDragState.notifyDropLw(newX, newY);
- }
- } else {
- synchronized (mWindowMap) {
- // move the surface and tell the involved window(s) where we are
- mDragState.notifyMoveLw(newX, newY);
- }
- }
- } break;
-
- case MotionEvent.ACTION_UP: {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "Got UP on move channel; dropping at "
- + newX + "," + newY);
- mMuteInput = true;
- synchronized (mWindowMap) {
- endDrag = mDragState.notifyDropLw(newX, newY);
- }
- } break;
-
- case MotionEvent.ACTION_CANCEL: {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
- mMuteInput = true;
- endDrag = true;
- } break;
- }
-
- if (endDrag) {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ended; tearing down state");
- // tell all the windows that the drag has ended
- synchronized (mWindowMap) {
- // endDragLw will post back to looper to dispose the receiver
- // since we still need the receiver for the last finishInputEvent.
- mDragState.endDragLw();
- }
- mStylusButtonDownAtStart = false;
- mIsStartEvent = true;
- }
-
- handled = true;
- }
- } catch (Exception e) {
- Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
- } finally {
- finishInputEvent(event, handled);
- }
- }
- }
-
/**
* Whether the UI is currently running in touch mode (not showing
* navigational focus because the user is directly pressing the screen).
@@ -929,15 +824,20 @@
/**
* Closes a surface transaction.
+ * @param where debug string indicating where the transaction originated
*/
- void closeSurfaceTransaction() {
+ void closeSurfaceTransaction(String where) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
synchronized (mWindowMap) {
- if (mRoot.mSurfaceTraceEnabled) {
- mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+ try {
+ traceStateLocked(where);
+ } finally {
+ if (mRoot.mSurfaceTraceEnabled) {
+ mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+ }
+ SurfaceControl.closeTransaction();
}
- SurfaceControl.closeTransaction();
}
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -1038,6 +938,12 @@
}, 0);
}
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver result) {
+ new WindowManagerShellCommand(this).exec(this, in, out, err, args, callback, result);
+ }
+
private WindowManagerService(Context context, InputManagerService inputManager,
boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
WindowManagerPolicy policy) {
@@ -1068,6 +974,8 @@
mPolicy = policy;
mTaskSnapshotController = new TaskSnapshotController(this);
+ mWindowTracing = WindowTracing.createDefaultAndStartLooper(context);
+
LocalServices.addService(WindowManagerPolicy.class, mPolicy);
if(mInputManager != null) {
@@ -1165,6 +1073,7 @@
mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
+ mDragDropController = new DragDropController(this, mH.getLooper());
LocalServices.addService(WindowManagerInternal.class, new LocalService());
initPolicy();
@@ -1176,7 +1085,7 @@
try {
createWatermarkInTransaction();
} finally {
- closeSurfaceTransaction();
+ closeSurfaceTransaction("createWatermarkInTransaction");
}
showEmulatorDisplayOverlayIfNeeded();
@@ -3392,7 +3301,7 @@
// Notify whether the docked stack exists for the current user
final DisplayContent displayContent = getDefaultDisplayContentLocked();
final TaskStack stack =
- displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ displayContent.getSplitScreenPrimaryStackIgnoringVisibility();
displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
stack != null && stack.hasTaskForUser(newUserId));
@@ -3680,7 +3589,7 @@
mCircularDisplayMask = null;
}
} finally {
- closeSurfaceTransaction();
+ closeSurfaceTransaction("showCircularMask");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION showCircularMask(visible=" + visible + ")");
}
@@ -3705,7 +3614,7 @@
}
mEmulatorDisplayOverlay.setVisibility(true);
} finally {
- closeSurfaceTransaction();
+ closeSurfaceTransaction("showEmulatorDisplayOverlay");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
"<<< CLOSE TRANSACTION showEmulatorDisplayOverlay");
}
@@ -3784,7 +3693,7 @@
* of the target image.
*/
@Override
- public boolean requestAssistScreenshot(final IAssistScreenshotReceiver receiver) {
+ public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
if (!checkCallingPermission(READ_FRAME_BUFFER,
"requestAssistScreenshot()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
@@ -3796,7 +3705,7 @@
1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
false /* includeDecor */);
try {
- receiver.send(bm);
+ receiver.onHandleAssistScreenshot(bm);
} catch (RemoteException e) {
}
});
@@ -4637,73 +4546,6 @@
}
// -------------------------------------------------------------
- // Drag and drop
- // -------------------------------------------------------------
-
- IBinder prepareDragSurface(IWindow window, SurfaceSession session,
- int flags, int width, int height, Surface outSurface) {
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height
- + " flags=" + Integer.toHexString(flags) + " win=" + window
- + " asbinder=" + window.asBinder());
- }
-
- final int callerPid = Binder.getCallingPid();
- final int callerUid = Binder.getCallingUid();
- final long origId = Binder.clearCallingIdentity();
- IBinder token = null;
-
- try {
- synchronized (mWindowMap) {
- try {
- if (mDragState == null) {
- // TODO(multi-display): support other displays
- final DisplayContent displayContent = getDefaultDisplayContentLocked();
- final Display display = displayContent.getDisplay();
-
- SurfaceControl surface = new SurfaceControl(session, "drag surface",
- width, height, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
- surface.setLayerStack(display.getLayerStack());
- float alpha = 1;
- if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
- alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
- }
- surface.setAlpha(alpha);
-
- if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG "
- + surface + ": CREATE");
- outSurface.copyFrom(surface);
- final IBinder winBinder = window.asBinder();
- token = new Binder();
- mDragState = new DragState(this, token, surface, flags, winBinder);
- mDragState.mPid = callerPid;
- mDragState.mUid = callerUid;
- mDragState.mOriginalAlpha = alpha;
- token = mDragState.mToken = new Binder();
-
- // 5 second timeout for this window to actually begin the drag
- mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder);
- Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder);
- mH.sendMessageDelayed(msg, 5000);
- } else {
- Slog.w(TAG_WM, "Drag already in progress");
- }
- } catch (OutOfResourcesException e) {
- Slog.e(TAG_WM, "Can't allocate drag surface w=" + width + " h=" + height, e);
- if (mDragState != null) {
- mDragState.reset();
- mDragState = null;
- }
- }
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
-
- return token;
- }
-
- // -------------------------------------------------------------
// Input Events and Focus Management
// -------------------------------------------------------------
@@ -4864,8 +4706,7 @@
public static final int APP_FREEZE_TIMEOUT = 17;
public static final int SEND_NEW_CONFIGURATION = 18;
public static final int REPORT_WINDOWS_CHANGE = 19;
- public static final int DRAG_START_TIMEOUT = 20;
- public static final int DRAG_END_TIMEOUT = 21;
+
public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
public static final int BOOT_TIMEOUT = 23;
public static final int WAITING_FOR_DRAWN_TIMEOUT = 24;
@@ -4891,8 +4732,6 @@
public static final int UPDATE_DOCKED_STACK_DIVIDER = 41;
- public static final int TEAR_DOWN_DRAG_AND_DROP_INPUT = 44;
-
public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
public static final int NOTIFY_APP_TRANSITION_STARTING = 47;
@@ -5120,48 +4959,6 @@
break;
}
- case DRAG_START_TIMEOUT: {
- IBinder win = (IBinder)msg.obj;
- if (DEBUG_DRAG) {
- Slog.w(TAG_WM, "Timeout starting drag by win " + win);
- }
- synchronized (mWindowMap) {
- // !!! TODO: ANR the app that has failed to start the drag in time
- if (mDragState != null) {
- mDragState.unregister();
- mDragState.reset();
- mDragState = null;
- }
- }
- break;
- }
-
- case DRAG_END_TIMEOUT: {
- IBinder win = (IBinder)msg.obj;
- if (DEBUG_DRAG) {
- Slog.w(TAG_WM, "Timeout ending drag to win " + win);
- }
- synchronized (mWindowMap) {
- // !!! TODO: ANR the drag-receiving app
- if (mDragState != null) {
- mDragState.mDragResult = false;
- mDragState.endDragLw();
- }
- }
- break;
- }
-
- case TEAR_DOWN_DRAG_AND_DROP_INPUT: {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ending; tearing down input channel");
- DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj;
- if (interceptor != null) {
- synchronized (mWindowMap) {
- interceptor.tearDown();
- }
- }
- }
- break;
-
case REPORT_HARD_KEYBOARD_STATUS_CHANGE: {
notifyHardKeyboardStatusChange();
break;
@@ -6355,9 +6152,10 @@
}
@Override
- public void createInputConsumer(String name, InputChannel inputChannel) {
+ public void createInputConsumer(IBinder token, String name, InputChannel inputChannel) {
synchronized (mWindowMap) {
- mInputMonitor.createInputConsumer(name, inputChannel);
+ mInputMonitor.createInputConsumer(token, name, inputChannel, Binder.getCallingPid(),
+ Binder.getCallingUserHandle());
}
}
@@ -6532,9 +6330,16 @@
}
}
- private void writeToProtoLocked(ProtoOutputStream proto) {
+ /**
+ * Write to a protocol buffer output stream. Protocol buffer message definition is at
+ * {@link com.android.server.wm.proto.WindowManagerServiceProto}.
+ *
+ * @param proto Stream to write the WindowContainer object to.
+ * @param trim If true, reduce the amount of data written.
+ */
+ void writeToProtoLocked(ProtoOutputStream proto, boolean trim) {
mPolicy.writeToProto(proto, POLICY);
- mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER);
+ mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER, trim);
if (mCurrentFocus != null) {
mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
}
@@ -6551,6 +6356,17 @@
mAppTransition.writeToProto(proto, APP_TRANSITION);
}
+ void traceStateLocked(String where) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
+ try {
+ mWindowTracing.traceStateLocked(where, this);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Exception while tracing state", e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
ArrayList<WindowState> windows) {
pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
@@ -6825,10 +6641,9 @@
PriorityDump.dump(mPriorityDumper, fd, pw, args);
}
- private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
boolean dumpAll = false;
- boolean useProto = false;
int opti = 0;
while (opti < args.length) {
@@ -6839,8 +6654,6 @@
opti++;
if ("-a".equals(opt)) {
dumpAll = true;
- } else if ("--proto".equals(opt)) {
- useProto = true;
} else if ("-h".equals(opt)) {
pw.println("Window manager dump options:");
pw.println(" [-a] [-h] [cmd] ...");
@@ -6870,7 +6683,7 @@
if (useProto) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
synchronized (mWindowMap) {
- writeToProtoLocked(proto);
+ writeToProtoLocked(proto, false /* trim */);
}
proto.flush();
return;
@@ -7122,7 +6935,7 @@
public int getDockedStackSide() {
synchronized (mWindowMap) {
final TaskStack dockedStack = getDefaultDisplayContentLocked()
- .getSplitScreenPrimaryStackStackIgnoringVisibility();
+ .getSplitScreenPrimaryStackIgnoringVisibility();
return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide();
}
}
@@ -7268,7 +7081,7 @@
}
synchronized (mWindowMap) {
- if (mDragState != null) {
+ if (mDragDropController.mDragState != null) {
// Drag cursor overrides the app cursor.
return;
}
@@ -7702,7 +7515,8 @@
}
boolean hasWideColorGamutSupport() {
- return mHasWideColorGamutSupport;
+ return mHasWideColorGamutSupport &&
+ !SystemProperties.getBoolean("persist.sys.sf.native_mode", false);
}
void updateNonSystemOverlayWindowsVisibilityIfNeeded(WindowState win, boolean surfaceShown) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
new file mode 100644
index 0000000..4b98d9d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * ShellCommands for WindowManagerService.
+ *
+ * Use with {@code adb shell cmd window ...}.
+ */
+public class WindowManagerShellCommand extends ShellCommand {
+
+ private final WindowManagerService mService;
+
+ public WindowManagerShellCommand(WindowManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ switch (cmd) {
+ case "tracing":
+ return mService.mWindowTracing.onShellCommand(this, getNextArgRequired());
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Window Manager (window) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(" tracing (start | stop)");
+ pw.println(" start or stop window tracing");
+ pw.println();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e171528..6b1932d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1849,110 +1849,109 @@
final long origId = Binder.clearCallingIdentity();
- disposeInputChannel();
+ try {
+ disposeInputChannel();
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Remove " + this
- + ": mSurfaceController=" + mWinAnimator.mSurfaceController
- + " mAnimatingExit=" + mAnimatingExit
- + " mRemoveOnExit=" + mRemoveOnExit
- + " mHasSurface=" + mHasSurface
- + " surfaceShowing=" + mWinAnimator.getShown()
- + " isAnimationSet=" + mWinAnimator.isAnimationSet()
- + " app-animation="
- + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
- + " mWillReplaceWindow=" + mWillReplaceWindow
- + " inPendingTransaction="
- + (mAppToken != null ? mAppToken.inPendingTransaction : false)
- + " mDisplayFrozen=" + mService.mDisplayFrozen
- + " callers=" + Debug.getCallers(6));
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Remove " + this
+ + ": mSurfaceController=" + mWinAnimator.mSurfaceController
+ + " mAnimatingExit=" + mAnimatingExit
+ + " mRemoveOnExit=" + mRemoveOnExit
+ + " mHasSurface=" + mHasSurface
+ + " surfaceShowing=" + mWinAnimator.getShown()
+ + " isAnimationSet=" + mWinAnimator.isAnimationSet()
+ + " app-animation="
+ + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
+ + " mWillReplaceWindow=" + mWillReplaceWindow
+ + " inPendingTransaction="
+ + (mAppToken != null ? mAppToken.inPendingTransaction : false)
+ + " mDisplayFrozen=" + mService.mDisplayFrozen
+ + " callers=" + Debug.getCallers(6));
- // Visibility of the removed window. Will be used later to update orientation later on.
- boolean wasVisible = false;
+ // Visibility of the removed window. Will be used later to update orientation later on.
+ boolean wasVisible = false;
- final int displayId = getDisplayId();
+ final int displayId = getDisplayId();
- // First, see if we need to run an animation. If we do, we have to hold off on removing the
- // window until the animation is done. If the display is frozen, just remove immediately,
- // since the animation wouldn't be seen.
- if (mHasSurface && mToken.okToAnimate()) {
- if (mWillReplaceWindow) {
- // This window is going to be replaced. We need to keep it around until the new one
- // gets added, then we will get rid of this one.
- if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Preserving " + this + " until the new one is " + "added");
- // TODO: We are overloading mAnimatingExit flag to prevent the window state from
- // been removed. We probably need another flag to indicate that window removal
- // should be deffered vs. overloading the flag that says we are playing an exit
- // animation.
- mAnimatingExit = true;
- mReplacingRemoveRequested = true;
- Binder.restoreCallingIdentity(origId);
- return;
- }
-
- // If we are not currently running the exit animation, we need to see about starting one
- wasVisible = isWinVisibleLw();
-
- if (keepVisibleDeadWindow) {
- if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Not removing " + this + " because app died while it's visible");
-
- mAppDied = true;
- setDisplayLayoutNeeded();
- mService.mWindowPlacerLocked.performSurfacePlacement();
-
- // Set up a replacement input channel since the app is now dead.
- // We need to catch tapping on the dead window to restart the app.
- openInputChannel(null);
- mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
-
- Binder.restoreCallingIdentity(origId);
- return;
- }
-
- if (wasVisible) {
- final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
-
- // Try starting an animation.
- if (mWinAnimator.applyAnimationLocked(transit, false)) {
+ // First, see if we need to run an animation. If we do, we have to hold off on removing the
+ // window until the animation is done. If the display is frozen, just remove immediately,
+ // since the animation wouldn't be seen.
+ if (mHasSurface && mToken.okToAnimate()) {
+ if (mWillReplaceWindow) {
+ // This window is going to be replaced. We need to keep it around until the new one
+ // gets added, then we will get rid of this one.
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "Preserving " + this + " until the new one is " + "added");
+ // TODO: We are overloading mAnimatingExit flag to prevent the window state from
+ // been removed. We probably need another flag to indicate that window removal
+ // should be deffered vs. overloading the flag that says we are playing an exit
+ // animation.
mAnimatingExit = true;
+ mReplacingRemoveRequested = true;
+ return;
}
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
- mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
- }
- }
- final boolean isAnimating =
- mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
- final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
- && mAppToken.isLastWindow(this);
- // We delay the removal of a window if it has a showing surface that can be used to run
- // exit animation and it is marked as exiting.
- // Also, If isn't the an animating starting window that is the last window in the app.
- // We allow the removal of the non-animating starting window now as there is no
- // additional window or animation that will trigger its removal.
- if (mWinAnimator.getShown() && mAnimatingExit
- && (!lastWindowIsStartingWindow || isAnimating)) {
- // The exit animation is running or should run... wait for it!
- if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
- "Not removing " + this + " due to exit animation ");
- setupWindowForRemoveOnExit();
- if (mAppToken != null) {
- mAppToken.updateReportedVisibilityLocked();
- }
- Binder.restoreCallingIdentity(origId);
- return;
- }
- }
- removeImmediately();
- // Removing a visible window will effect the computed orientation
- // So just update orientation if needed.
- if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
- mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+ // If we are not currently running the exit animation, we need to see about starting one
+ wasVisible = isWinVisibleLw();
+
+ if (keepVisibleDeadWindow) {
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "Not removing " + this + " because app died while it's visible");
+
+ mAppDied = true;
+ setDisplayLayoutNeeded();
+ mService.mWindowPlacerLocked.performSurfacePlacement();
+
+ // Set up a replacement input channel since the app is now dead.
+ // We need to catch tapping on the dead window to restart the app.
+ openInputChannel(null);
+ mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+ return;
+ }
+
+ if (wasVisible) {
+ final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
+
+ // Try starting an animation.
+ if (mWinAnimator.applyAnimationLocked(transit, false)) {
+ mAnimatingExit = true;
+ }
+ //TODO (multidisplay): Magnification is supported only for the default display.
+ if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
+ mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
+ }
+ }
+ final boolean isAnimating =
+ mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
+ final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
+ && mAppToken.isLastWindow(this);
+ // We delay the removal of a window if it has a showing surface that can be used to run
+ // exit animation and it is marked as exiting.
+ // Also, If isn't the an animating starting window that is the last window in the app.
+ // We allow the removal of the non-animating starting window now as there is no
+ // additional window or animation that will trigger its removal.
+ if (mWinAnimator.getShown() && mAnimatingExit
+ && (!lastWindowIsStartingWindow || isAnimating)) {
+ // The exit animation is running or should run... wait for it!
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+ "Not removing " + this + " due to exit animation ");
+ setupWindowForRemoveOnExit();
+ if (mAppToken != null) {
+ mAppToken.updateReportedVisibilityLocked();
+ }
+ return;
+ }
+ }
+
+ removeImmediately();
+ // Removing a visible window will effect the computed orientation
+ // So just update orientation if needed.
+ if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
+ mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+ }
+ mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
- Binder.restoreCallingIdentity(origId);
}
private void setupWindowForRemoveOnExit() {
@@ -2361,7 +2360,7 @@
// also reset drag resizing state, because the owner can't do it
// anymore.
final TaskStack stack =
- dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ dc.getSplitScreenPrimaryStackIgnoringVisibility();
if (stack != null) {
stack.resetDockedStackToMiddle();
}
@@ -3120,9 +3119,9 @@
@CallSuper
@Override
- public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
final long token = proto.start(fieldId);
- super.writeToProto(proto, WINDOW_CONTAINER);
+ super.writeToProto(proto, WINDOW_CONTAINER, trim);
writeIdentifierToProto(proto, IDENTIFIER);
proto.write(DISPLAY_ID, getDisplayId());
proto.write(STACK_ID, getStackId());
@@ -3137,7 +3136,7 @@
mWinAnimator.writeToProto(proto, ANIMATOR);
proto.write(ANIMATING_EXIT, mAnimatingExit);
for (int i = 0; i < mChildren.size(); i++) {
- mChildren.get(i).writeToProto(proto, CHILD_WINDOWS);
+ mChildren.get(i).writeToProto(proto, CHILD_WINDOWS, trim);
}
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 5266903..86397ae 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -732,7 +732,7 @@
mSurfaceController.setLayerStackInTransaction(getLayerStack());
mSurfaceController.setLayer(mAnimLayer);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("createSurfaceLocked");
}
mLastHidden = true;
@@ -1711,7 +1711,7 @@
Slog.w(TAG, "Error positioning surface of " + mWin
+ " pos=(" + left + "," + top + ")", e);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setWallpaperOffset");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION setWallpaperOffset");
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index d56df55..a214523 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -101,8 +101,14 @@
mWindowSession = win.mSession;
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+ final SurfaceControl.Builder b = new SurfaceControl.Builder(s)
+ .setName(name)
+ .setSize(w, h)
+ .setFormat(format)
+ .setFlags(flags)
+ .setMetadata(windowType, ownerUid);
mSurfaceControl = new SurfaceControlWithBackground(
- s, name, w, h, format, flags, windowType, ownerUid, this);
+ name, b, windowType, w, h, this);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (mService.mRoot.mSurfaceTraceEnabled) {
@@ -246,14 +252,14 @@
if (mAnimator.mWin.usesRelativeZOrdering()) {
mSurfaceControl.setRelativeLayer(
mAnimator.mWin.getParentWindow()
- .mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(),
+ .mWinAnimator.mSurfaceController.mSurfaceControl,
-1);
} else {
mSurfaceLayer = layer;
mSurfaceControl.setLayer(layer);
}
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setLayer");
}
}
}
@@ -379,7 +385,7 @@
try {
mSurfaceControl.setTransparentRegionHint(region);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setTransparentRegion");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION setTransparentRegion");
}
@@ -397,7 +403,7 @@
try {
mSurfaceControl.setOpaque(isOpaque);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setOpaqueLocked");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked");
}
}
@@ -414,7 +420,7 @@
try {
mSurfaceControl.setSecure(isSecure);
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("setSecure");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked");
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index fa33fe8..cd5e475 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
package com.android.server.wm;
@@ -414,7 +429,7 @@
try {
mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
} finally {
- mService.closeSurfaceTransaction();
+ mService.closeSurfaceTransaction("handleAppTransitionReadyLocked");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
}
@@ -689,11 +704,14 @@
// Create a new surface for the thumbnail
WindowState window = appToken.findMainWindow();
- SurfaceControl surfaceControl = new SurfaceControl(mService.mFxSession,
- "thumbnail anim", dirty.width(), dirty.height(),
- PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN,
- appToken.windowType,
- window != null ? window.mOwnerUid : Binder.getCallingUid());
+ final SurfaceControl surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+ .setName("thumbnail anim")
+ .setSize(dirty.width(), dirty.height())
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .setMetadata(appToken.windowType,
+ window != null ? window.mOwnerUid : Binder.getCallingUid())
+ .build();
+
surfaceControl.setLayerStack(display.getLayerStack());
if (SHOW_TRANSACTIONS) {
Slog.i(TAG, " THUMBNAIL " + surfaceControl + ": CREATE");
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 943448e..62a2abb 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -267,13 +267,13 @@
@CallSuper
@Override
- public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
final long token = proto.start(fieldId);
- super.writeToProto(proto, WINDOW_CONTAINER);
+ super.writeToProto(proto, WINDOW_CONTAINER, trim);
proto.write(HASH_CODE, System.identityHashCode(this));
for (int i = 0; i < mChildren.size(); i++) {
final WindowState w = mChildren.get(i);
- w.writeToProto(proto, WINDOWS);
+ w.writeToProto(proto, WINDOWS, trim);
}
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
new file mode 100644
index 0000000..5657f6c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.ENTRY;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.proto.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
+import static com.android.server.wm.proto.WindowManagerTraceProto.WHERE;
+import static com.android.server.wm.proto.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
+
+import android.content.Context;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * A class that allows window manager to dump its state continuously to a trace file, such that a
+ * time series of window manager state can be analyzed after the fact.
+ */
+class WindowTracing {
+
+ private static final String TAG = "WindowTracing";
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private final Object mLock = new Object();
+ private final File mTraceFile;
+ private final BlockingQueue<ProtoOutputStream> mWriteQueue = new ArrayBlockingQueue<>(200);
+
+ private boolean mEnabled;
+ private volatile boolean mEnabledLockFree;
+
+ WindowTracing(File file) {
+ mTraceFile = file;
+ }
+
+ void startTrace(PrintWriter pw) throws IOException {
+ synchronized (mLock) {
+ logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
+ mWriteQueue.clear();
+ mTraceFile.delete();
+ try (OutputStream os = new FileOutputStream(mTraceFile)) {
+ ProtoOutputStream proto = new ProtoOutputStream(os);
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ proto.flush();
+ }
+ mEnabled = mEnabledLockFree = true;
+ }
+ }
+
+ private void logAndPrintln(PrintWriter pw, String msg) {
+ Log.i(TAG, msg);
+ pw.println(msg);
+ pw.flush();
+ }
+
+ void stopTrace(PrintWriter pw) {
+ synchronized (mLock) {
+ logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+ mEnabled = mEnabledLockFree = false;
+ while (!mWriteQueue.isEmpty()) {
+ if (mEnabled) {
+ logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
+ throw new IllegalStateException("tracing enabled while waiting for flush.");
+ }
+ try {
+ mLock.wait();
+ mLock.notify();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+ }
+ }
+
+ void appendTraceEntry(ProtoOutputStream proto) {
+ if (!mEnabledLockFree) {
+ return;
+ }
+
+ if (!mWriteQueue.offer(proto)) {
+ Log.e(TAG, "Dropping window trace entry, queue full");
+ }
+ }
+
+ void loop() {
+ for (;;) {
+ loopOnce();
+ }
+ }
+
+ @VisibleForTesting
+ void loopOnce() {
+ ProtoOutputStream proto;
+ try {
+ proto = mWriteQueue.take();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return;
+ }
+
+ synchronized (mLock) {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
+ try (OutputStream os = new FileOutputStream(mTraceFile, true /* append */)) {
+ os.write(proto.getBytes());
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write file " + mTraceFile, e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ mLock.notify();
+ }
+ }
+
+ boolean isEnabled() {
+ return mEnabledLockFree;
+ }
+
+ static WindowTracing createDefaultAndStartLooper(Context context) {
+ File file = new File("/data/system/window_trace.proto");
+ WindowTracing windowTracing = new WindowTracing(file);
+ new Thread(windowTracing::loop, "window_tracing").start();
+ return windowTracing;
+ }
+
+ int onShellCommand(ShellCommand shell, String cmd) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ try {
+ switch (cmd) {
+ case "start":
+ startTrace(pw);
+ return 0;
+ case "stop":
+ stopTrace(pw);
+ return 0;
+ default:
+ pw.println("Unknown command: " + cmd);
+ return -1;
+ }
+ } catch (IOException e) {
+ logAndPrintln(pw, e.toString());
+ throw new RuntimeException(e);
+ }
+ }
+
+ void traceStateLocked(String where, WindowManagerService service) {
+ if (!isEnabled()) {
+ return;
+ }
+ ProtoOutputStream os = new ProtoOutputStream();
+ long tokenOuter = os.start(ENTRY);
+ os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+ os.write(WHERE, where);
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked");
+ try {
+ long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
+ service.writeToProtoLocked(os, true /* trim */);
+ os.end(tokenInner);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ os.end(tokenOuter);
+ appendTraceEntry(os);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+}
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index 3901ceb..d4ffa70 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -48,8 +48,6 @@
status_t err;
- configureRpcThreadpool(5, false /* callerWillJoin */);
-
JavaVM *vm;
LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM");
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index e8ef168..2f45181 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -92,7 +92,6 @@
register_android_server_tv_TvUinputBridge(env);
register_android_server_tv_TvInputHal(env);
register_android_server_PersistentDataBlockService(env);
- register_android_server_Watchdog(env);
register_android_server_HardwarePropertiesManagerService(env);
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
diff --git a/services/coverage/java/com/android/server/coverage/CoverageService.java b/services/coverage/java/com/android/server/coverage/CoverageService.java
index d600aa8c..ed8d24a 100644
--- a/services/coverage/java/com/android/server/coverage/CoverageService.java
+++ b/services/coverage/java/com/android/server/coverage/CoverageService.java
@@ -112,7 +112,7 @@
}
// Try to open the destination file
- ParcelFileDescriptor fd = openOutputFileForSystem(dest);
+ ParcelFileDescriptor fd = openFileForSystem(dest, "w");
if (fd == null) {
return -1;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 80f6a4b..2d8a0ee 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.BIND_DEVICE_ADMIN;
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED;
import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE;
@@ -225,6 +226,8 @@
private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component";
+ private static final String TAG_LOCK_TASK_FEATURES = "lock-task-features";
+
private static final String TAG_STATUS_BAR = "statusbar";
private static final String ATTR_DISABLED = "disabled";
@@ -507,6 +510,9 @@
// This is the list of component allowed to start lock task mode.
List<String> mLockTaskPackages = new ArrayList<>();
+ // Bitfield of feature flags to be enabled during LockTask mode.
+ int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+
boolean mStatusBarDisabled = false;
ComponentName mRestrictionsProvider;
@@ -2628,6 +2634,12 @@
out.endTag(null, TAG_LOCK_TASK_COMPONENTS);
}
+ if (policy.mLockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
+ out.startTag(null, TAG_LOCK_TASK_FEATURES);
+ out.attribute(null, ATTR_VALUE, Integer.toString(policy.mLockTaskFeatures));
+ out.endTag(null, TAG_LOCK_TASK_FEATURES);
+ }
+
if (policy.mStatusBarDisabled) {
out.startTag(null, TAG_STATUS_BAR);
out.attribute(null, ATTR_DISABLED, Boolean.toString(policy.mStatusBarDisabled));
@@ -2868,6 +2880,9 @@
policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME));
} else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name"));
+ } else if (TAG_LOCK_TASK_FEATURES.equals(tag)) {
+ policy.mLockTaskFeatures = Integer.parseInt(
+ parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_STATUS_BAR.equals(tag)) {
policy.mStatusBarDisabled = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_DISABLED));
@@ -2936,6 +2951,7 @@
validatePasswordOwnerLocked(policy);
updateMaximumTimeToLockLocked(userHandle);
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
+ updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle);
if (policy.mStatusBarDisabled) {
setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
}
@@ -2953,6 +2969,18 @@
}
}
+ private void updateLockTaskFeaturesLocked(int flags, int userId) {
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ mInjector.getIActivityManager()
+ .updateLockTaskFeatures(userId, flags);
+ } catch (RemoteException e) {
+ // Not gonna happen.
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+
private void updateDeviceOwnerLocked() {
long ident = mInjector.binderClearCallingIdentity();
try {
@@ -6939,6 +6967,7 @@
policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
policy.mAffiliationIds.clear();
policy.mLockTaskPackages.clear();
+ policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
saveSettingsLocked(userId);
try {
@@ -8921,25 +8950,6 @@
updateLockTaskPackagesLocked(packages, userHandle);
}
- private void maybeClearLockTaskPackagesLocked() {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
- final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
- for (int i = 0; i < userInfos.size(); i++) {
- int userId = userInfos.get(i).id;
- final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages;
- if (!lockTaskPackages.isEmpty() &&
- !isUserAffiliatedWithDeviceLocked(userId)) {
- Slog.d(LOG_TAG,
- "User id " + userId + " not affiliated. Clearing lock task packages");
- setLockTaskPackagesLocked(userId, Collections.<String>emptyList());
- }
- }
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
- }
-
@Override
public String[] getLockTaskPackages(ComponentName who) {
Preconditions.checkNotNull(who, "ComponentName is null");
@@ -8966,12 +8976,82 @@
}
@Override
+ public void setLockTaskFeatures(ComponentName who, int flags) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
+ throw new SecurityException("Admin " + who +
+ " is neither the device owner or affiliated user's profile owner.");
+ }
+ setLockTaskFeaturesLocked(userHandle, flags);
+ }
+ }
+
+ private void setLockTaskFeaturesLocked(int userHandle, int flags) {
+ DevicePolicyData policy = getUserData(userHandle);
+ policy.mLockTaskFeatures = flags;
+ saveSettingsLocked(userHandle);
+ updateLockTaskFeaturesLocked(flags, userHandle);
+ }
+
+ @Override
+ public int getLockTaskFeatures(ComponentName who) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
+ throw new SecurityException("Admin " + who +
+ " is neither the device owner or affiliated user's profile owner.");
+ }
+ return getUserData(userHandle).mLockTaskFeatures;
+ }
+ }
+
+ private void maybeClearLockTaskPolicyLocked() {
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
+ final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
+ for (int i = userInfos.size() - 1; i >= 0; i--) {
+ int userId = userInfos.get(i).id;
+ if (isUserAffiliatedWithDeviceLocked(userId)) {
+ continue;
+ }
+
+ final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages;
+ if (!lockTaskPackages.isEmpty()) {
+ Slog.d(LOG_TAG,
+ "User id " + userId + " not affiliated. Clearing lock task packages");
+ setLockTaskPackagesLocked(userId, Collections.<String>emptyList());
+ }
+ final int lockTaskFeatures = getUserData(userId).mLockTaskFeatures;
+ if (lockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE){
+ Slog.d(LOG_TAG,
+ "User id " + userId + " not affiliated. Clearing lock task features");
+ setLockTaskFeaturesLocked(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+ }
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) {
if (!isCallerWithSystemUid()) {
throw new SecurityException("notifyLockTaskModeChanged can only be called by system");
}
synchronized (this) {
final DevicePolicyData policy = getUserData(userHandle);
+
+ if (policy.mStatusBarDisabled) {
+ // Status bar is managed by LockTaskController during LockTask, so we cancel this
+ // policy when LockTask starts, and reapply it when LockTask ends
+ setStatusBarDisabledInternal(!isEnabled, userHandle);
+ }
+
Bundle adminExtras = new Bundle();
adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg);
for (ActiveAdmin admin : policy.mAdminList) {
@@ -9182,8 +9262,17 @@
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
DevicePolicyData policy = getUserData(userId);
if (policy.mStatusBarDisabled != disabled) {
- if (!setStatusBarDisabledInternal(disabled, userId)) {
- return false;
+ boolean isLockTaskMode = false;
+ try {
+ isLockTaskMode = mInjector.getIActivityManager().getLockTaskModeState()
+ != LOCK_TASK_MODE_NONE;
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Failed to get LockTask mode");
+ }
+ if (!isLockTaskMode) {
+ if (!setStatusBarDisabledInternal(disabled, userId)) {
+ return false;
+ }
}
policy.mStatusBarDisabled = disabled;
saveSettingsLocked(userId);
@@ -10283,7 +10372,7 @@
// but as a result of that other users might become affiliated or un-affiliated.
maybePauseDeviceWideLoggingLocked();
maybeResumeDeviceWideLoggingLocked();
- maybeClearLockTaskPackagesLocked();
+ maybeClearLockTaskPolicyLocked();
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index d9db22e..a2c2aeb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -125,7 +125,10 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
-import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
+import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.view.Display.DEFAULT_DISPLAY;
public final class SystemServer {
@@ -542,11 +545,9 @@
traceEnd();
// Bring up recovery system in case a rescue party needs a reboot
- if (!SystemProperties.getBoolean("config.disable_noncore", false)) {
- traceBeginAndSlog("StartRecoverySystemService");
- mSystemServiceManager.startService(RecoverySystemService.class);
- traceEnd();
- }
+ traceBeginAndSlog("StartRecoverySystemService");
+ mSystemServiceManager.startService(RecoverySystemService.class);
+ traceEnd();
// Now that we have the bare essentials of the OS up and running, take
// note that we just booted, which might send out a rescue party if
@@ -657,6 +658,9 @@
mSystemServiceManager.startService(DropBoxManagerService.class);
traceEnd();
+ // First hwbinder call is in BatteryService.
+ android.os.HwBinder.startRpcThreadPool(5, false /* callerWillJoin */);
+
traceBeginAndSlog("StartBatteryService");
// Tracks the battery level. Requires LightService.
mSystemServiceManager.startService(BatteryService.class);
@@ -702,13 +706,7 @@
MmsServiceBroker mmsService = null;
HardwarePropertiesManagerService hardwarePropertiesService = null;
- boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
- boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
- boolean disableLocation = SystemProperties.getBoolean("config.disable_location", false);
boolean disableSystemUI = SystemProperties.getBoolean("config.disable_systemui", false);
- boolean disableNonCoreServices = SystemProperties.getBoolean("config.disable_noncore", false);
- boolean disableNetwork = SystemProperties.getBoolean("config.disable_network", false);
- boolean disableNetworkTime = SystemProperties.getBoolean("config.disable_networktime", false);
boolean disableRtt = SystemProperties.getBoolean("config.disable_rtt", false);
boolean disableMediaProjection = SystemProperties.getBoolean("config.disable_mediaproj",
false);
@@ -827,7 +825,7 @@
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore, new PhoneWindowManager());
ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
- DUMP_PRIORITY_CRITICAL);
+ DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
traceEnd();
@@ -872,8 +870,6 @@
} else if (!context.getPackageManager().hasSystemFeature
(PackageManager.FEATURE_BLUETOOTH)) {
Slog.i(TAG, "No Bluetooth Service (Bluetooth Hardware Not Present)");
- } else if (disableBluetooth) {
- Slog.i(TAG, "Bluetooth Service disabled by config");
} else {
traceBeginAndSlog("StartBluetoothService");
mSystemServiceManager.startService(BluetoothService.class);
@@ -924,8 +920,7 @@
traceEnd();
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
- if (!disableStorage &&
- !"0".equals(SystemProperties.get("system_init.startmountservice"))) {
+ if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) {
traceBeginAndSlog("StartStorageManagerService");
try {
/*
@@ -975,42 +970,40 @@
traceEnd();
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartLockSettingsService");
- try {
- mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
- lockSettings = ILockSettings.Stub.asInterface(
- ServiceManager.getService("lock_settings"));
- } catch (Throwable e) {
- reportWtf("starting LockSettingsService service", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartLockSettingsService");
+ try {
+ mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
+ lockSettings = ILockSettings.Stub.asInterface(
+ ServiceManager.getService("lock_settings"));
+ } catch (Throwable e) {
+ reportWtf("starting LockSettingsService service", e);
+ }
+ traceEnd();
- final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("");
- if (hasPdb) {
- traceBeginAndSlog("StartPersistentDataBlock");
- mSystemServiceManager.startService(PersistentDataBlockService.class);
- traceEnd();
- }
-
- if (hasPdb || OemLockService.isHalPresent()) {
- // Implementation depends on pdb or the OemLock HAL
- traceBeginAndSlog("StartOemLockService");
- mSystemServiceManager.startService(OemLockService.class);
- traceEnd();
- }
-
- traceBeginAndSlog("StartDeviceIdleController");
- mSystemServiceManager.startService(DeviceIdleController.class);
- traceEnd();
-
- // Always start the Device Policy Manager, so that the API is compatible with
- // API8.
- traceBeginAndSlog("StartDevicePolicyManager");
- mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
+ final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("");
+ if (hasPdb) {
+ traceBeginAndSlog("StartPersistentDataBlock");
+ mSystemServiceManager.startService(PersistentDataBlockService.class);
traceEnd();
}
+ if (hasPdb || OemLockService.isHalPresent()) {
+ // Implementation depends on pdb or the OemLock HAL
+ traceBeginAndSlog("StartOemLockService");
+ mSystemServiceManager.startService(OemLockService.class);
+ traceEnd();
+ }
+
+ traceBeginAndSlog("StartDeviceIdleController");
+ mSystemServiceManager.startService(DeviceIdleController.class);
+ traceEnd();
+
+ // Always start the Device Policy Manager, so that the API is compatible with
+ // API8.
+ traceBeginAndSlog("StartDevicePolicyManager");
+ mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
+ traceEnd();
+
if (!disableSystemUI) {
traceBeginAndSlog("StartStatusBarManagerService");
try {
@@ -1022,152 +1015,146 @@
traceEnd();
}
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartClipboardService");
- mSystemServiceManager.startService(ClipboardService.class);
- traceEnd();
+ traceBeginAndSlog("StartClipboardService");
+ mSystemServiceManager.startService(ClipboardService.class);
+ traceEnd();
+
+ traceBeginAndSlog("StartNetworkManagementService");
+ try {
+ networkManagement = NetworkManagementService.create(context);
+ ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
+ } catch (Throwable e) {
+ reportWtf("starting NetworkManagement Service", e);
}
+ traceEnd();
- if (!disableNetwork) {
- traceBeginAndSlog("StartNetworkManagementService");
- try {
- networkManagement = NetworkManagementService.create(context);
- ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
- } catch (Throwable e) {
- reportWtf("starting NetworkManagement Service", e);
- }
- traceEnd();
-
- traceBeginAndSlog("StartIpSecService");
- try {
- ipSecService = IpSecService.create(context);
- ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
- } catch (Throwable e) {
- reportWtf("starting IpSec Service", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartIpSecService");
+ try {
+ ipSecService = IpSecService.create(context);
+ ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
+ } catch (Throwable e) {
+ reportWtf("starting IpSec Service", e);
}
+ traceEnd();
- if (!disableNonCoreServices && !disableTextServices) {
+ if (!disableTextServices) {
traceBeginAndSlog("StartTextServicesManager");
mSystemServiceManager.startService(TextServicesManagerService.Lifecycle.class);
traceEnd();
}
- if (!disableNetwork) {
- traceBeginAndSlog("StartNetworkScoreService");
- try {
- networkScore = new NetworkScoreService(context);
- ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore);
- } catch (Throwable e) {
- reportWtf("starting Network Score Service", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartNetworkScoreService");
+ try {
+ networkScore = new NetworkScoreService(context);
+ ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore);
+ } catch (Throwable e) {
+ reportWtf("starting Network Score Service", e);
+ }
+ traceEnd();
- traceBeginAndSlog("StartNetworkStatsService");
- try {
- networkStats = NetworkStatsService.create(context, networkManagement);
- ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
- } catch (Throwable e) {
- reportWtf("starting NetworkStats Service", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartNetworkStatsService");
+ try {
+ networkStats = NetworkStatsService.create(context, networkManagement);
+ ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
+ } catch (Throwable e) {
+ reportWtf("starting NetworkStats Service", e);
+ }
+ traceEnd();
- traceBeginAndSlog("StartNetworkPolicyManagerService");
- try {
- networkPolicy = new NetworkPolicyManagerService(context,
- mActivityManagerService, networkStats, networkManagement);
- ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
- } catch (Throwable e) {
- reportWtf("starting NetworkPolicy Service", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartNetworkPolicyManagerService");
+ try {
+ networkPolicy = new NetworkPolicyManagerService(context,
+ mActivityManagerService, networkStats, networkManagement);
+ ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
+ } catch (Throwable e) {
+ reportWtf("starting NetworkPolicy Service", e);
+ }
+ traceEnd();
- // Wifi Service must be started first for wifi-related services.
- traceBeginAndSlog("StartWifi");
- mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
- traceEnd();
- traceBeginAndSlog("StartWifiScanning");
- mSystemServiceManager.startService(
- "com.android.server.wifi.scanner.WifiScanningService");
- traceEnd();
+ // Wifi Service must be started first for wifi-related services.
+ traceBeginAndSlog("StartWifi");
+ mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
+ traceEnd();
+ traceBeginAndSlog("StartWifiScanning");
+ mSystemServiceManager.startService(
+ "com.android.server.wifi.scanner.WifiScanningService");
+ traceEnd();
- if (!disableRtt) {
- traceBeginAndSlog("StartWifiRtt");
- mSystemServiceManager.startService("com.android.server.wifi.RttService");
- traceEnd();
-
- if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_RTT)) {
- traceBeginAndSlog("StartRttService");
- mSystemServiceManager.startService(
- "com.android.server.wifi.rtt.RttService");
- traceEnd();
- }
- }
+ if (!disableRtt) {
+ traceBeginAndSlog("StartWifiRtt");
+ mSystemServiceManager.startService("com.android.server.wifi.RttService");
+ traceEnd();
if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_AWARE)) {
- traceBeginAndSlog("StartWifiAware");
- mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
+ PackageManager.FEATURE_WIFI_RTT)) {
+ traceBeginAndSlog("StartRttService");
+ mSystemServiceManager.startService(
+ "com.android.server.wifi.rtt.RttService");
traceEnd();
}
+ }
- if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_DIRECT)) {
- traceBeginAndSlog("StartWifiP2P");
- mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
- traceEnd();
- }
-
- if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_LOWPAN)) {
- traceBeginAndSlog("StartLowpan");
- mSystemServiceManager.startService(LOWPAN_SERVICE_CLASS);
- traceEnd();
- }
-
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
- mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
- traceBeginAndSlog("StartEthernet");
- mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
- traceEnd();
- }
-
- traceBeginAndSlog("StartConnectivityService");
- try {
- connectivity = new ConnectivityService(
- context, networkManagement, networkStats, networkPolicy);
- ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
- networkStats.bindConnectivityManager(connectivity);
- networkPolicy.bindConnectivityManager(connectivity);
- } catch (Throwable e) {
- reportWtf("starting Connectivity Service", e);
- }
- traceEnd();
-
- traceBeginAndSlog("StartNsdService");
- try {
- serviceDiscovery = NsdService.create(context);
- ServiceManager.addService(
- Context.NSD_SERVICE, serviceDiscovery);
- } catch (Throwable e) {
- reportWtf("starting Service Discovery Service", e);
- }
+ if (context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_AWARE)) {
+ traceBeginAndSlog("StartWifiAware");
+ mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
traceEnd();
}
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartUpdateLockService");
- try {
- ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
- new UpdateLockService(context));
- } catch (Throwable e) {
- reportWtf("starting UpdateLockService", e);
- }
+ if (context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_DIRECT)) {
+ traceBeginAndSlog("StartWifiP2P");
+ mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
traceEnd();
}
+ if (context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_LOWPAN)) {
+ traceBeginAndSlog("StartLowpan");
+ mSystemServiceManager.startService(LOWPAN_SERVICE_CLASS);
+ traceEnd();
+ }
+
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
+ traceBeginAndSlog("StartEthernet");
+ mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
+ traceEnd();
+ }
+
+ traceBeginAndSlog("StartConnectivityService");
+ try {
+ connectivity = new ConnectivityService(
+ context, networkManagement, networkStats, networkPolicy);
+ ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,
+ /* allowIsolated= */ false,
+ DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+ networkStats.bindConnectivityManager(connectivity);
+ networkPolicy.bindConnectivityManager(connectivity);
+ } catch (Throwable e) {
+ reportWtf("starting Connectivity Service", e);
+ }
+ traceEnd();
+
+ traceBeginAndSlog("StartNsdService");
+ try {
+ serviceDiscovery = NsdService.create(context);
+ ServiceManager.addService(
+ Context.NSD_SERVICE, serviceDiscovery);
+ } catch (Throwable e) {
+ reportWtf("starting Service Discovery Service", e);
+ }
+ traceEnd();
+
+ traceBeginAndSlog("StartUpdateLockService");
+ try {
+ ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
+ new UpdateLockService(context));
+ } catch (Throwable e) {
+ reportWtf("starting UpdateLockService", e);
+ }
+ traceEnd();
+
traceBeginAndSlog("StartNotificationManager");
mSystemServiceManager.startService(NotificationManagerService.class);
SystemNotificationChannels.createAll(context);
@@ -1180,27 +1167,25 @@
mSystemServiceManager.startService(DeviceStorageMonitorService.class);
traceEnd();
- if (!disableLocation) {
- traceBeginAndSlog("StartLocationManagerService");
- try {
- location = new LocationManagerService(context);
- ServiceManager.addService(Context.LOCATION_SERVICE, location);
- } catch (Throwable e) {
- reportWtf("starting Location Manager", e);
- }
- traceEnd();
-
- traceBeginAndSlog("StartCountryDetectorService");
- try {
- countryDetector = new CountryDetectorService(context);
- ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
- } catch (Throwable e) {
- reportWtf("starting Country Detector", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartLocationManagerService");
+ try {
+ location = new LocationManagerService(context);
+ ServiceManager.addService(Context.LOCATION_SERVICE, location);
+ } catch (Throwable e) {
+ reportWtf("starting Location Manager", e);
}
+ traceEnd();
- if (!disableNonCoreServices && !disableSearchManager) {
+ traceBeginAndSlog("StartCountryDetectorService");
+ try {
+ countryDetector = new CountryDetectorService(context);
+ ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
+ } catch (Throwable e) {
+ reportWtf("starting Country Detector", e);
+ }
+ traceEnd();
+
+ if (!disableSearchManager) {
traceBeginAndSlog("StartSearchManagerService");
try {
mSystemServiceManager.startService(SEARCH_MANAGER_SERVICE_CLASS);
@@ -1210,8 +1195,7 @@
traceEnd();
}
- if (!disableNonCoreServices && context.getResources().getBoolean(
- R.bool.config_enableWallpaperService)) {
+ if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {
traceBeginAndSlog("StartWallpaperManagerService");
mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
traceEnd();
@@ -1227,16 +1211,14 @@
traceEnd();
}
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartDockObserver");
- mSystemServiceManager.startService(DockObserver.class);
- traceEnd();
+ traceBeginAndSlog("StartDockObserver");
+ mSystemServiceManager.startService(DockObserver.class);
+ traceEnd();
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- traceBeginAndSlog("StartThermalObserver");
- mSystemServiceManager.startService(THERMAL_OBSERVER_CLASS);
- traceEnd();
- }
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ traceBeginAndSlog("StartThermalObserver");
+ mSystemServiceManager.startService(THERMAL_OBSERVER_CLASS);
+ traceEnd();
}
traceBeginAndSlog("StartWiredAccessoryManager");
@@ -1249,46 +1231,44 @@
}
traceEnd();
- if (!disableNonCoreServices) {
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
- // Start MIDI Manager service
- traceBeginAndSlog("StartMidiManager");
- mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
- traceEnd();
- }
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+ // Start MIDI Manager service
+ traceBeginAndSlog("StartMidiManager");
+ mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
+ traceEnd();
+ }
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
- || mPackageManager.hasSystemFeature(
- PackageManager.FEATURE_USB_ACCESSORY)) {
- // Manage USB host and device support
- traceBeginAndSlog("StartUsbService");
- mSystemServiceManager.startService(USB_SERVICE_CLASS);
- traceEnd();
- }
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
+ || mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_USB_ACCESSORY)) {
+ // Manage USB host and device support
+ traceBeginAndSlog("StartUsbService");
+ mSystemServiceManager.startService(USB_SERVICE_CLASS);
+ traceEnd();
+ }
- if (!disableSerial) {
- traceBeginAndSlog("StartSerialService");
- try {
- // Serial port support
- serial = new SerialService(context);
- ServiceManager.addService(Context.SERIAL_SERVICE, serial);
- } catch (Throwable e) {
- Slog.e(TAG, "Failure starting SerialService", e);
- }
- traceEnd();
- }
-
- traceBeginAndSlog("StartHardwarePropertiesManagerService");
+ if (!disableSerial) {
+ traceBeginAndSlog("StartSerialService");
try {
- hardwarePropertiesService = new HardwarePropertiesManagerService(context);
- ServiceManager.addService(Context.HARDWARE_PROPERTIES_SERVICE,
- hardwarePropertiesService);
+ // Serial port support
+ serial = new SerialService(context);
+ ServiceManager.addService(Context.SERIAL_SERVICE, serial);
} catch (Throwable e) {
- Slog.e(TAG, "Failure starting HardwarePropertiesManagerService", e);
+ Slog.e(TAG, "Failure starting SerialService", e);
}
traceEnd();
}
+ traceBeginAndSlog("StartHardwarePropertiesManagerService");
+ try {
+ hardwarePropertiesService = new HardwarePropertiesManagerService(context);
+ ServiceManager.addService(Context.HARDWARE_PROPERTIES_SERVICE,
+ hardwarePropertiesService);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting HardwarePropertiesManagerService", e);
+ }
+ traceEnd();
+
traceBeginAndSlog("StartTwilightService");
mSystemServiceManager.startService(TwilightService.class);
traceEnd();
@@ -1307,48 +1287,46 @@
mSystemServiceManager.startService(SoundTriggerService.class);
traceEnd();
- if (!disableNonCoreServices) {
- if (!disableTrustManager) {
- traceBeginAndSlog("StartTrustManager");
- mSystemServiceManager.startService(TrustManagerService.class);
- traceEnd();
- }
-
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
- traceBeginAndSlog("StartBackupManager");
- mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
- traceEnd();
- }
-
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
- || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
- traceBeginAndSlog("StartAppWidgerService");
- mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
- traceEnd();
- }
-
- // We need to always start this service, regardless of whether the
- // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
- // of initializing various settings. It will internally modify its behavior
- // based on that feature.
- traceBeginAndSlog("StartVoiceRecognitionManager");
- mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
- traceEnd();
-
- if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
- traceBeginAndSlog("StartGestureLauncher");
- mSystemServiceManager.startService(GestureLauncherService.class);
- traceEnd();
- }
- traceBeginAndSlog("StartSensorNotification");
- mSystemServiceManager.startService(SensorNotificationService.class);
- traceEnd();
-
- traceBeginAndSlog("StartContextHubSystemService");
- mSystemServiceManager.startService(ContextHubSystemService.class);
+ if (!disableTrustManager) {
+ traceBeginAndSlog("StartTrustManager");
+ mSystemServiceManager.startService(TrustManagerService.class);
traceEnd();
}
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
+ traceBeginAndSlog("StartBackupManager");
+ mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
+ traceEnd();
+ }
+
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
+ || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
+ traceBeginAndSlog("StartAppWidgerService");
+ mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
+ traceEnd();
+ }
+
+ // We need to always start this service, regardless of whether the
+ // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
+ // of initializing various settings. It will internally modify its behavior
+ // based on that feature.
+ traceBeginAndSlog("StartVoiceRecognitionManager");
+ mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+ traceEnd();
+
+ if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
+ traceBeginAndSlog("StartGestureLauncher");
+ mSystemServiceManager.startService(GestureLauncherService.class);
+ traceEnd();
+ }
+ traceBeginAndSlog("StartSensorNotification");
+ mSystemServiceManager.startService(SensorNotificationService.class);
+ traceEnd();
+
+ traceBeginAndSlog("StartContextHubSystemService");
+ mSystemServiceManager.startService(ContextHubSystemService.class);
+ traceEnd();
+
traceBeginAndSlog("StartDiskStatsService");
try {
ServiceManager.addService("diskstats", new DiskStatsService(context));
@@ -1370,16 +1348,14 @@
traceEnd();
}
- if (!disableNetwork && !disableNetworkTime) {
- traceBeginAndSlog("StartNetworkTimeUpdateService");
- try {
- networkTimeUpdater = new NetworkTimeUpdateService(context);
- ServiceManager.addService("network_time_update_service", networkTimeUpdater);
- } catch (Throwable e) {
- reportWtf("starting NetworkTimeUpdate service", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartNetworkTimeUpdateService");
+ try {
+ networkTimeUpdater = new NetworkTimeUpdateService(context);
+ ServiceManager.addService("network_time_update_service", networkTimeUpdater);
+ } catch (Throwable e) {
+ reportWtf("starting NetworkTimeUpdate service", e);
}
+ traceEnd();
traceBeginAndSlog("StartCommonTimeManagementService");
try {
@@ -1390,38 +1366,32 @@
}
traceEnd();
- if (!disableNetwork) {
- traceBeginAndSlog("CertBlacklister");
- try {
- CertBlacklister blacklister = new CertBlacklister(context);
- } catch (Throwable e) {
- reportWtf("starting CertBlacklister", e);
- }
- traceEnd();
+ traceBeginAndSlog("CertBlacklister");
+ try {
+ CertBlacklister blacklister = new CertBlacklister(context);
+ } catch (Throwable e) {
+ reportWtf("starting CertBlacklister", e);
}
+ traceEnd();
- if (!disableNetwork && !disableNonCoreServices && EmergencyAffordanceManager.ENABLED) {
+ if (EmergencyAffordanceManager.ENABLED) {
// EmergencyMode service
traceBeginAndSlog("StartEmergencyAffordanceService");
mSystemServiceManager.startService(EmergencyAffordanceService.class);
traceEnd();
}
- if (!disableNonCoreServices) {
- // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
- traceBeginAndSlog("StartDreamManager");
- mSystemServiceManager.startService(DreamManagerService.class);
- traceEnd();
- }
+ // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
+ traceBeginAndSlog("StartDreamManager");
+ mSystemServiceManager.startService(DreamManagerService.class);
+ traceEnd();
- if (!disableNonCoreServices) {
- traceBeginAndSlog("AddGraphicsStatsService");
- ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
- new GraphicsStatsService(context));
- traceEnd();
- }
+ traceBeginAndSlog("AddGraphicsStatsService");
+ ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
+ new GraphicsStatsService(context));
+ traceEnd();
- if (!disableNonCoreServices && CoverageService.ENABLED) {
+ if (CoverageService.ENABLED) {
traceBeginAndSlog("AddCoverageService");
ServiceManager.addService(CoverageService.COVERAGE_SERVICE, new CoverageService());
traceEnd();
@@ -1472,38 +1442,37 @@
traceEnd();
}
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartMediaRouterService");
- try {
- mediaRouter = new MediaRouterService(context);
- ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
- } catch (Throwable e) {
- reportWtf("starting MediaRouterService", e);
- }
- traceEnd();
+ traceBeginAndSlog("StartMediaRouterService");
+ try {
+ mediaRouter = new MediaRouterService(context);
+ ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
+ } catch (Throwable e) {
+ reportWtf("starting MediaRouterService", e);
+ }
+ traceEnd();
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
- traceBeginAndSlog("StartFingerprintSensor");
- mSystemServiceManager.startService(FingerprintService.class);
- traceEnd();
- }
-
- traceBeginAndSlog("StartBackgroundDexOptService");
- try {
- BackgroundDexOptService.schedule(context);
- } catch (Throwable e) {
- reportWtf("starting StartBackgroundDexOptService", e);
- }
- traceEnd();
-
- traceBeginAndSlog("StartPruneInstantAppsJobService");
- try {
- PruneInstantAppsJobService.schedule(context);
- } catch (Throwable e) {
- reportWtf("StartPruneInstantAppsJobService", e);
- }
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ traceBeginAndSlog("StartFingerprintSensor");
+ mSystemServiceManager.startService(FingerprintService.class);
traceEnd();
}
+
+ traceBeginAndSlog("StartBackgroundDexOptService");
+ try {
+ BackgroundDexOptService.schedule(context);
+ } catch (Throwable e) {
+ reportWtf("starting StartBackgroundDexOptService", e);
+ }
+ traceEnd();
+
+ traceBeginAndSlog("StartPruneInstantAppsJobService");
+ try {
+ PruneInstantAppsJobService.schedule(context);
+ } catch (Throwable e) {
+ reportWtf("StartPruneInstantAppsJobService", e);
+ }
+ traceEnd();
+
// LauncherAppsService uses ShortcutService.
traceBeginAndSlog("StartShortcutServiceLifecycle");
mSystemServiceManager.startService(ShortcutService.Lifecycle.class);
@@ -1514,7 +1483,7 @@
traceEnd();
}
- if (!disableNonCoreServices && !disableMediaProjection) {
+ if (!disableMediaProjection) {
traceBeginAndSlog("StartMediaProjectionManager");
mSystemServiceManager.startService(MediaProjectionManagerService.class);
traceEnd();
@@ -1525,17 +1494,15 @@
mSystemServiceManager.startService(WEAR_CONNECTIVITY_SERVICE_CLASS);
traceEnd();
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartWearTimeService");
- mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
- mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
- traceEnd();
+ traceBeginAndSlog("StartWearTimeService");
+ mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
+ mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
+ traceEnd();
- if (enableLeftyService) {
- traceBeginAndSlog("StartWearLeftyService");
- mSystemServiceManager.startService(WEAR_LEFTY_SERVICE_CLASS);
- traceEnd();
- }
+ if (enableLeftyService) {
+ traceBeginAndSlog("StartWearLeftyService");
+ mSystemServiceManager.startService(WEAR_LEFTY_SERVICE_CLASS);
+ traceEnd();
}
}
@@ -1551,6 +1518,11 @@
traceEnd();
}
+ // Statsd helper
+ traceBeginAndSlog("StartStatsCompanionService");
+ mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
+ traceEnd();
+
// Before things start rolling, be sure we have decided whether
// we are in safe mode.
final boolean safeMode = wm.detectSafeMode();
diff --git a/services/net/java/android/net/util/VersionedBroadcastListener.java b/services/net/java/android/net/util/VersionedBroadcastListener.java
new file mode 100644
index 0000000..107c404
--- /dev/null
+++ b/services/net/java/android/net/util/VersionedBroadcastListener.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+
+/**
+ * A utility class that runs the provided callback on the provided handler when
+ * intents matching the provided filter arrive. Intents received by a stale
+ * receiver are safely ignored.
+ *
+ * Calls to startListening() and stopListening() must happen on the same thread.
+ *
+ * @hide
+ */
+public class VersionedBroadcastListener {
+ private static final boolean DBG = false;
+
+ public interface IntentCallback {
+ public void run(Intent intent);
+ }
+
+ private final String mTag;
+ private final Context mContext;
+ private final Handler mHandler;
+ private final IntentFilter mFilter;
+ private final Consumer<Intent> mCallback;
+ private final AtomicInteger mGenerationNumber;
+ private BroadcastReceiver mReceiver;
+
+ public VersionedBroadcastListener(String tag, Context ctx, Handler handler,
+ IntentFilter filter, Consumer<Intent> callback) {
+ mTag = tag;
+ mContext = ctx;
+ mHandler = handler;
+ mFilter = filter;
+ mCallback = callback;
+ mGenerationNumber = new AtomicInteger(0);
+ }
+
+ public void startListening() {
+ if (DBG) Log.d(mTag, "startListening");
+ if (mReceiver != null) return;
+
+ mReceiver = new Receiver(mTag, mGenerationNumber, mCallback);
+ mContext.registerReceiver(mReceiver, mFilter, null, mHandler);
+ }
+
+ public void stopListening() {
+ if (DBG) Log.d(mTag, "stopListening");
+ if (mReceiver == null) return;
+
+ mGenerationNumber.incrementAndGet();
+ mContext.unregisterReceiver(mReceiver);
+ mReceiver = null;
+ }
+
+ private static class Receiver extends BroadcastReceiver {
+ public final String tag;
+ public final AtomicInteger atomicGenerationNumber;
+ public final Consumer<Intent> callback;
+ // Used to verify this receiver is still current.
+ public final int generationNumber;
+
+ public Receiver(
+ String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
+ this.tag = tag;
+ this.atomicGenerationNumber = atomicGenerationNumber;
+ this.callback = callback;
+ generationNumber = atomicGenerationNumber.incrementAndGet();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int currentGenerationNumber = atomicGenerationNumber.get();
+
+ if (DBG) {
+ Log.d(tag, "receiver generationNumber=" + generationNumber +
+ ", current generationNumber=" + currentGenerationNumber);
+ }
+ if (generationNumber != currentGenerationNumber) return;
+
+ callback.accept(intent);
+ }
+ }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9f5f856..70e8a16 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -20,10 +20,12 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@@ -72,6 +74,7 @@
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.util.ArrayMap;
@@ -113,7 +116,7 @@
private IPackageManager mPackageManager;
@Mock
private PackageManager mPackageManagerClient;
- private Context mContext = getContext();
+ private TestableContext mContext = spy(getContext());
private final String PKG = mContext.getPackageName();
private TestableLooper mTestableLooper;
@Mock
@@ -174,15 +177,18 @@
mTestableLooper = TestableLooper.get(this);
mHandler = mService.new WorkerHandler(mTestableLooper.getLooper());
// MockPackageManager - default returns ApplicationInfo with matching calling UID
+ mContext.setMockPackageManager(mPackageManagerClient);
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = mUid;
when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
+ when(mPackageManagerClient.getPackageUidAsUser(any(), anyInt())).thenReturn(mUid);
final LightsManager mockLightsManager = mock(LightsManager.class);
when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class));
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+ when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
// write to a test file; the system file isn't readable from tests
mFile = new File(mContext.getCacheDir(), "test.xml");
@@ -227,6 +233,7 @@
mBinderService.createNotificationChannels(
PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
+ assertNotNull(mBinderService.getNotificationChannel(PKG, TEST_CHANNEL_ID));
}
@After
@@ -1338,6 +1345,72 @@
}
@Test
+ public void testSetListenerAccessForUser() throws Exception {
+ UserHandle user = UserHandle.of(10);
+ ComponentName c = ComponentName.unflattenFromString("package/Component");
+ try {
+ mBinderService.setNotificationListenerAccessGrantedForUser(
+ c, user.getIdentifier(), true);
+ } catch (SecurityException e) {
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
+
+ verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any());
+ verify(mListeners, times(1)).setPackageOrComponentEnabled(
+ c.flattenToString(), user.getIdentifier(), true, true);
+ verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+ c.flattenToString(), user.getIdentifier(), false, true);
+ verify(mAssistants, never()).setPackageOrComponentEnabled(
+ any(), anyInt(), anyBoolean(), anyBoolean());
+ }
+
+ @Test
+ public void testSetAssistantAccessForUser() throws Exception {
+ UserHandle user = UserHandle.of(10);
+ ComponentName c = ComponentName.unflattenFromString("package/Component");
+ try {
+ mBinderService.setNotificationAssistantAccessGrantedForUser(
+ c, user.getIdentifier(), true);
+ } catch (SecurityException e) {
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
+
+ verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any());
+ verify(mAssistants, times(1)).setPackageOrComponentEnabled(
+ c.flattenToString(), user.getIdentifier(), true, true);
+ verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+ c.flattenToString(), user.getIdentifier(), false, true);
+ verify(mListeners, never()).setPackageOrComponentEnabled(
+ any(), anyInt(), anyBoolean(), anyBoolean());
+ }
+
+ @Test
+ public void testSetDndAccessForUser() throws Exception {
+ UserHandle user = UserHandle.of(10);
+ ComponentName c = ComponentName.unflattenFromString("package/Component");
+ try {
+ mBinderService.setNotificationPolicyAccessGrantedForUser(
+ c.getPackageName(), user.getIdentifier(), true);
+ } catch (SecurityException e) {
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
+
+ verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any());
+ verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+ c.getPackageName(), user.getIdentifier(), true, true);
+ verify(mAssistants, never()).setPackageOrComponentEnabled(
+ any(), anyInt(), anyBoolean(), anyBoolean());
+ verify(mListeners, never()).setPackageOrComponentEnabled(
+ any(), anyInt(), anyBoolean(), anyBoolean());
+ }
+
+ @Test
public void testSetListenerAccess() throws Exception {
ComponentName c = ComponentName.unflattenFromString("package/Component");
try {
@@ -1401,9 +1474,9 @@
mBinderService.setNotificationListenerAccessGranted(c, true);
verify(mListeners, never()).setPackageOrComponentEnabled(
- c.flattenToString(), 0, true, true);
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
verify(mConditionProviders, never()).setPackageOrComponentEnabled(
- c.flattenToString(), 0, false, true);
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
verify(mAssistants, never()).setPackageOrComponentEnabled(
any(), anyInt(), anyBoolean(), anyBoolean());
}
@@ -1414,11 +1487,10 @@
ComponentName c = ComponentName.unflattenFromString("package/Component");
mBinderService.setNotificationAssistantAccessGranted(c, true);
-
verify(mListeners, never()).setPackageOrComponentEnabled(
- c.flattenToString(), 0, true, true);
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
verify(mConditionProviders, never()).setPackageOrComponentEnabled(
- c.flattenToString(), 0, false, true);
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
verify(mAssistants, never()).setPackageOrComponentEnabled(
any(), anyInt(), anyBoolean(), anyBoolean());
}
@@ -1430,14 +1502,77 @@
mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true);
verify(mListeners, never()).setPackageOrComponentEnabled(
- c.flattenToString(), 0, true, true);
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
verify(mConditionProviders, never()).setPackageOrComponentEnabled(
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
+ verify(mAssistants, never()).setPackageOrComponentEnabled(
+ any(), anyInt(), anyBoolean(), anyBoolean());
+ }
+
+ @Test
+ public void testSetListenerAccess_doesNothingOnLowRam_exceptWatch() throws Exception {
+ when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(true);
+ when(mActivityManager.isLowRamDevice()).thenReturn(true);
+ ComponentName c = ComponentName.unflattenFromString("package/Component");
+ try {
+ mBinderService.setNotificationListenerAccessGranted(c, true);
+ } catch (SecurityException e) {
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
+
+ verify(mListeners, times(1)).setPackageOrComponentEnabled(
+ c.flattenToString(), 0, true, true);
+ verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
c.flattenToString(), 0, false, true);
verify(mAssistants, never()).setPackageOrComponentEnabled(
any(), anyInt(), anyBoolean(), anyBoolean());
}
@Test
+ public void testSetAssistantAccess_doesNothingOnLowRam_exceptWatch() throws Exception {
+ when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(true);
+ when(mActivityManager.isLowRamDevice()).thenReturn(true);
+ ComponentName c = ComponentName.unflattenFromString("package/Component");
+ try {
+ mBinderService.setNotificationAssistantAccessGranted(c, true);
+ } catch (SecurityException e) {
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
+
+ verify(mListeners, never()).setPackageOrComponentEnabled(
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
+ verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+ c.flattenToString(), 0, false, true);
+ verify(mAssistants, times(1)).setPackageOrComponentEnabled(
+ c.flattenToString(), 0, true, true);
+ }
+
+ @Test
+ public void testSetDndAccess_doesNothingOnLowRam_exceptWatch() throws Exception {
+ when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(true);
+ when(mActivityManager.isLowRamDevice()).thenReturn(true);
+ ComponentName c = ComponentName.unflattenFromString("package/Component");
+ try {
+ mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true);
+ } catch (SecurityException e) {
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
+
+ verify(mListeners, never()).setPackageOrComponentEnabled(
+ anyString(), anyInt(), anyBoolean(), anyBoolean());
+ verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+ c.getPackageName(), 0, true, true);
+ verify(mAssistants, never()).setPackageOrComponentEnabled(
+ any(), anyInt(), anyBoolean(), anyBoolean());
+ }
+
+ @Test
public void testOnlyAutogroupIfGroupChanged_noPriorNoti_autogroups() throws Exception {
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
mService.addEnqueuedNotification(r);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index 679be1d..cb13e85 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -51,13 +51,8 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityRecordTests extends ActivityTestsBase {
- private final ComponentName testActivityComponent =
- ComponentName.unflattenFromString("com.foo/.BarActivity");
- private final ComponentName secondaryActivityComponent =
- ComponentName.unflattenFromString("com.foo/.BarActivity2");
-
private ActivityManagerService mService;
- private ActivityStack mStack;
+ private TestActivityStack mStack;
private TaskRecord mTask;
private ActivityRecord mActivity;
@@ -69,20 +64,20 @@
mService = createActivityManagerService();
mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- mTask = createTask(mService.mStackSupervisor, testActivityComponent, mStack);
- mActivity = createActivity(mService, testActivityComponent, mTask);
+ mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+ mActivity = new ActivityBuilder(mService).setTask(mTask).build();
}
@Test
public void testStackCleanupOnClearingTask() throws Exception {
mActivity.setTask(null);
- assertEquals(getActivityRemovedFromStackCount(), 1);
+ assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1);
}
@Test
public void testStackCleanupOnActivityRemoval() throws Exception {
mTask.removeActivity(mActivity);
- assertEquals(getActivityRemovedFromStackCount(), 1);
+ assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1);
}
@Test
@@ -94,10 +89,10 @@
@Test
public void testNoCleanupMovingActivityInSameStack() throws Exception {
- final TaskRecord newTask =
- createTask(mService.mStackSupervisor, testActivityComponent, mStack);
+ final TaskRecord newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack)
+ .build();
mActivity.reparent(newTask, 0, null /*reason*/);
- assertEquals(getActivityRemovedFromStackCount(), 0);
+ assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0);
}
@Test
@@ -129,15 +124,6 @@
assertEquals(expectedActivityBounds, mActivity.getBounds());
}
- private int getActivityRemovedFromStackCount() {
- if (mStack instanceof ActivityStackReporter) {
- return ((ActivityStackReporter) mStack).onActivityRemovedFromStackInvocationCount();
- }
-
- return -1;
- }
-
-
@Test
public void testCanBeLaunchedOnDisplay() throws Exception {
testSupportsLaunchingResizeable(false /*taskPresent*/, true /*taskResizeable*/,
@@ -158,13 +144,13 @@
mService.mSupportsMultiWindow = true;
final TaskRecord task = taskPresent
- ? createTask(mService.mStackSupervisor, testActivityComponent, mStack) : null;
+ ? new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build() : null;
if (task != null) {
task.setResizeMode(taskResizeable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE);
}
- final ActivityRecord record = createActivity(mService, secondaryActivityComponent, task);
+ final ActivityRecord record = new ActivityBuilder(mService).setTask(task).build();
record.info.resizeMode = activityResizeable
? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE;
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
index 60a55fa..fc17da8 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -51,9 +51,6 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityStackSupervisorTests extends ActivityTestsBase {
- private final ComponentName testActivityComponent =
- ComponentName.unflattenFromString("com.foo/.BarActivity");
-
private ActivityManagerService mService;
private ActivityStackSupervisor mSupervisor;
private ActivityStack mFullscreenStack;
@@ -87,15 +84,14 @@
*/
@Test
public void testReplacingTaskInPinnedStack() throws Exception {
- final TaskRecord firstTask = createTask(
- mSupervisor, testActivityComponent, mFullscreenStack);
- final ActivityRecord firstActivity = createActivity(mService, testActivityComponent,
- firstTask);
- // Create a new task on the full screen stack
- final TaskRecord secondTask = createTask(
- mSupervisor, testActivityComponent, mFullscreenStack);
- final ActivityRecord secondActivity = createActivity(mService, testActivityComponent,
- secondTask);
+ final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+ .setStack(mFullscreenStack).build();
+ final TaskRecord firstTask = firstActivity.getTask();
+
+ final ActivityRecord secondActivity = new ActivityBuilder(mService).setCreateTask(true)
+ .setStack(mFullscreenStack).build();
+ final TaskRecord secondTask = secondActivity.getTask();
+
mSupervisor.setFocusStackUnchecked("testReplacingTaskInPinnedStack", mFullscreenStack);
// Ensure full screen stack has both tasks.
@@ -104,7 +100,7 @@
// Move first activity to pinned stack.
final Rect sourceBounds = new Rect();
mSupervisor.moveActivityToPinnedStackLocked(firstActivity, sourceBounds,
- 0f /*aspectRatio*/, false, "initialMove");
+ 0f /*aspectRatio*/, "initialMove");
final ActivityDisplay display = mFullscreenStack.getDisplay();
ActivityStack pinnedStack = display.getPinnedStack();
@@ -114,7 +110,7 @@
// Move second activity to pinned stack.
mSupervisor.moveActivityToPinnedStackLocked(secondActivity, sourceBounds,
- 0f /*aspectRatio*/, false, "secondMove");
+ 0f /*aspectRatio*/, "secondMove");
// Need to get stacks again as a new instance might have been created.
pinnedStack = display.getPinnedStack();
@@ -142,10 +138,8 @@
*/
@Test
public void testStoppingActivityRemovedWhenResumed() throws Exception {
- final TaskRecord firstTask = createTask(
- mSupervisor, testActivityComponent, mFullscreenStack);
- final ActivityRecord firstActivity = createActivity(mService, testActivityComponent,
- firstTask);
+ final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+ .setStack(mFullscreenStack).build();
mSupervisor.mStoppingActivities.add(firstActivity);
firstActivity.completeResumeLocked();
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index 4ee1f47..480b210 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -16,13 +16,21 @@
package com.android.server.am;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+
import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
@@ -45,12 +53,6 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityStackTests extends ActivityTestsBase {
- private static final int TEST_STACK_ID = 100;
- private static final ComponentName testActivityComponent =
- ComponentName.unflattenFromString("com.foo/.BarActivity");
- private static final ComponentName testOverlayComponent =
- ComponentName.unflattenFromString("com.foo/.OverlayActivity");
-
private ActivityManagerService mService;
private ActivityStackSupervisor mSupervisor;
private ActivityStack mStack;
@@ -65,7 +67,7 @@
mSupervisor = mService.mStackSupervisor;
mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- mTask = createTask(mSupervisor, testActivityComponent, mStack);
+ mTask = new TaskBuilder(mSupervisor).setStack(mStack).build();
}
@Test
@@ -77,7 +79,7 @@
@Test
public void testOccupiedTaskCleanupOnRemove() throws Exception {
- final ActivityRecord r = createActivity(mService, testActivityComponent, mTask);
+ final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
assertNotNull(mTask.getWindowContainerController());
mStack.removeTask(mTask, "testOccupiedTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING);
assertNotNull(mTask.getWindowContainerController());
@@ -85,7 +87,7 @@
@Test
public void testNoPauseDuringResumeTopActivity() throws Exception {
- final ActivityRecord r = createActivity(mService, testActivityComponent, mTask);
+ final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
// Simulate the a resumed activity set during
// {@link ActivityStack#resumeTopActivityUncheckedLocked}.
@@ -103,7 +105,7 @@
@Test
public void testStopActivityWhenActivityDestroyed() throws Exception {
- final ActivityRecord r = createActivity(mService, testActivityComponent, mTask);
+ final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
r.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
mSupervisor.setFocusStackUnchecked("testStopActivityWithDestroy", mStack);
mStack.stopActivityLocked(r);
@@ -113,18 +115,140 @@
@Test
public void testFindTaskWithOverlay() throws Exception {
- final ActivityRecord r = createActivity(mService, testActivityComponent, mTask, 0);
+ final ActivityRecord r = new ActivityBuilder(mService)
+ .setCreateTask(true)
+ .setStack(mStack)
+ .setUid(0)
+ .build();
+ final TaskRecord task = r.getTask();
// Overlay must be for a different user to prevent recognizing a matching top activity
- final ActivityRecord taskOverlay = createActivity(mService, testOverlayComponent, mTask,
- UserHandle.PER_USER_RANGE * 2);
+ final ActivityRecord taskOverlay = new ActivityBuilder(mService).setTask(task)
+ .setUid(UserHandle.PER_USER_RANGE * 2).build();
taskOverlay.mTaskOverlay = true;
final ActivityStackSupervisor.FindTaskResult result =
new ActivityStackSupervisor.FindTaskResult();
mStack.findTaskLocked(r, result);
- assertEquals(mTask.getTopActivity(false /* includeOverlays */), r);
- assertEquals(mTask.getTopActivity(true /* includeOverlays */), taskOverlay);
+ assertEquals(task.getTopActivity(false /* includeOverlays */), r);
+ assertEquals(task.getTopActivity(true /* includeOverlays */), taskOverlay);
assertNotNull(result.r);
}
+
+ @Test
+ public void testShouldBeVisible_Fullscreen() throws Exception {
+ final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+ final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+ final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+ assertTrue(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+
+ final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack
+ // should be visible since it is always on-top.
+ fullscreenStack.setIsTranslucent(false);
+ assertFalse(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+ assertTrue(fullscreenStack.shouldBeVisible(null /* starting */));
+
+ // Home stack should be visible behind a translucent fullscreen stack.
+ fullscreenStack.setIsTranslucent(true);
+ assertTrue(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+ }
+
+ @Test
+ public void testShouldBeVisible_SplitScreen() throws Exception {
+ final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+ final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+ final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+ // Home stack shouldn't be visible if both halves of split-screen are opaque.
+ splitScreenPrimary.setIsTranslucent(false);
+ splitScreenSecondary.setIsTranslucent(false);
+ assertFalse(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+
+ // Home stack should be visible if one of the halves of split-screen is translucent.
+ splitScreenPrimary.setIsTranslucent(true);
+ assertTrue(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+
+ final TestActivityStack splitScreenSecondary2 = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ // First split-screen secondary shouldn't be visible behind another opaque split-split
+ // secondary.
+ splitScreenSecondary2.setIsTranslucent(false);
+ assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+ // First split-screen secondary should be visible behind another translucent split-split
+ // secondary.
+ splitScreenSecondary2.setIsTranslucent(true);
+ assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+ final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
+
+ // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack.
+ assistantStack.setIsTranslucent(false);
+ assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+ assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
+ assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
+ assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+ // Split-screen stacks should be visible behind a translucent fullscreen stack.
+ assistantStack.setIsTranslucent(true);
+ assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+ assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+ }
+
+ @Test
+ public void testShouldBeVisible_Finishing() throws Exception {
+ final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+ final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+ final TestActivityStack translucentStack = createStackForShouldBeVisibleTest(display,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ translucentStack.setIsTranslucent(true);
+
+ assertTrue(homeStack.shouldBeVisible(null /* starting */));
+ assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+
+ final ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked();
+ topRunningHomeActivity.finishing = true;
+ final ActivityRecord topRunningTranslucentActivity =
+ translucentStack.topRunningActivityLocked();
+ topRunningTranslucentActivity.finishing = true;
+
+ // Home shouldn't be visible since its activity is marked as finishing and it isn't the top
+ // of the stack list.
+ assertFalse(homeStack.shouldBeVisible(null /* starting */));
+ // Home should be visible if we are starting an activity within it.
+ assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */));
+ // The translucent stack should be visible since it is the top of the stack list even though
+ // it has its activity marked as finishing.
+ assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+ }
+
+ private <T extends ActivityStack> T createStackForShouldBeVisibleTest(
+ ActivityDisplay display, int windowingMode, int activityType, boolean onTop) {
+ final T stack = display.createStack(windowingMode, activityType, onTop);
+ final ActivityRecord r = new ActivityBuilder(mService).setUid(0).setStack(stack)
+ .setCreateTask(true).build();
+ return stack;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index 026abce..5b1e4b7 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -50,9 +50,6 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityStarterTests extends ActivityTestsBase {
- private static final ComponentName testActivityComponent =
- ComponentName.unflattenFromString("com.foo/.BarActivity");
-
private ActivityManagerService mService;
private ActivityStarter mStarter;
@@ -66,9 +63,10 @@
@Test
public void testUpdateLaunchBounds() throws Exception {
// When in a non-resizeable stack, the task bounds should be updated.
- final TaskRecord task = createTask(mService.mStackSupervisor, testActivityComponent,
- mService.mStackSupervisor.getDefaultDisplay().createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */));
+ final TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
+ .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */))
+ .build();
final Rect bounds = new Rect(10, 10, 100, 100);
mStarter.updateBounds(task, bounds);
@@ -76,9 +74,10 @@
assertEquals(task.getStack().mBounds, null);
// When in a resizeable stack, the stack bounds should be updated as well.
- final TaskRecord task2 = createTask(mService.mStackSupervisor, testActivityComponent,
- mService.mStackSupervisor.getDefaultDisplay().createStack(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */));
+ final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor)
+ .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */))
+ .build();
assertTrue(task2.getStack() instanceof PinnedActivityStack);
mStarter.updateBounds(task2, bounds);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 20077f3..198cc6d 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
import static org.mockito.Mockito.mock;
@@ -52,12 +54,20 @@
* A base class to handle common operations in activity related unit tests.
*/
public class ActivityTestsBase {
+ private static boolean sOneTimeSetupDone = false;
+
private final Context mContext = InstrumentationRegistry.getContext();
private HandlerThread mHandlerThread;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
+ if (!sOneTimeSetupDone) {
+ sOneTimeSetupDone = true;
+
+ // Allows to mock package local classes and methods
+ System.setProperty("dexmaker.share_classloader", "true");
+ MockitoAnnotations.initMocks(this);
+ }
mHandlerThread = new HandlerThread("ActivityTestsBaseThread");
mHandlerThread.start();
}
@@ -68,62 +78,165 @@
}
protected ActivityManagerService createActivityManagerService() {
- final ActivityManagerService service = spy(new TestActivityManagerService(mContext));
+ return setupActivityManagerService(new TestActivityManagerService(mContext));
+ }
+
+ protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) {
+ service = spy(service);
service.mWindowManager = prepareMockWindowManager();
return service;
}
- protected static ActivityRecord createActivity(ActivityManagerService service,
- ComponentName component, TaskRecord task) {
- return createActivity(service, component, task, 0 /* userId */);
- }
+ /**
+ * Builder for creating new activities.
+ */
+ protected static class ActivityBuilder {
+ // An id appended to the end of the component name to make it unique
+ private static int sCurrentActivityId = 0;
- protected static ActivityRecord createActivity(ActivityManagerService service,
- ComponentName component, TaskRecord task, int uid) {
- Intent intent = new Intent();
- intent.setComponent(component);
- final ActivityInfo aInfo = new ActivityInfo();
- aInfo.applicationInfo = new ApplicationInfo();
- aInfo.applicationInfo.packageName = component.getPackageName();
- aInfo.applicationInfo.uid = uid;
- AttributeCache.init(service.mContext);
- final ActivityRecord activity = new ActivityRecord(service, null /* caller */,
- 0 /* launchedFromPid */, 0, null, intent, null,
- aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
- 0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */,
- service.mStackSupervisor, null /* options */, null /* sourceRecord */);
- activity.mWindowContainerController = mock(AppWindowContainerController.class);
+ // Default package name
+ private static final String DEFAULT_PACKAGE = "com.foo";
- if (task != null) {
- task.addActivityToTop(activity);
+ // Default base activity name
+ private static final String DEFAULT_BASE_ACTIVITY_NAME = ".BarActivity";
+
+ private final ActivityManagerService mService;
+
+ private ComponentName mComponent;
+ private TaskRecord mTaskRecord;
+ private int mUid;
+ private boolean mCreateTask;
+ private ActivityStack mStack;
+
+ ActivityBuilder(ActivityManagerService service) {
+ mService = service;
}
- return activity;
+ ActivityBuilder setComponent(ComponentName component) {
+ mComponent = component;
+ return this;
+ }
+
+ ActivityBuilder setTask(TaskRecord task) {
+ mTaskRecord = task;
+ return this;
+ }
+
+ ActivityBuilder setStack(ActivityStack stack) {
+ mStack = stack;
+ return this;
+ }
+
+ ActivityBuilder setCreateTask(boolean createTask) {
+ mCreateTask = createTask;
+ return this;
+ }
+
+ ActivityBuilder setUid(int uid) {
+ mUid = uid;
+ return this;
+ }
+
+ ActivityRecord build() {
+ if (mComponent == null) {
+ final int id = sCurrentActivityId++;
+ mComponent = ComponentName.createRelative(DEFAULT_PACKAGE,
+ DEFAULT_BASE_ACTIVITY_NAME + id);
+ }
+
+ if (mCreateTask) {
+ mTaskRecord = new TaskBuilder(mService.mStackSupervisor)
+ .setComponent(mComponent)
+ .setStack(mStack).build();
+ }
+
+ Intent intent = new Intent();
+ intent.setComponent(mComponent);
+ final ActivityInfo aInfo = new ActivityInfo();
+ aInfo.applicationInfo = new ApplicationInfo();
+ aInfo.applicationInfo.packageName = mComponent.getPackageName();
+ aInfo.applicationInfo.uid = mUid;
+ AttributeCache.init(mService.mContext);
+ final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
+ 0 /* launchedFromPid */, 0, null, intent, null,
+ aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
+ 0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */,
+ mService.mStackSupervisor, null /* options */, null /* sourceRecord */);
+ activity.mWindowContainerController = mock(AppWindowContainerController.class);
+
+ if (mTaskRecord != null) {
+ mTaskRecord.addActivityToTop(activity);
+ }
+
+ return activity;
+ }
}
- protected static TaskRecord createTask(ActivityStackSupervisor supervisor,
- ComponentName component, ActivityStack stack) {
- return createTask(supervisor, component, 0 /* flags */, 0 /* taskId */, stack);
- }
+ /**
+ * Builder for creating new tasks.
+ */
+ protected static class TaskBuilder {
+ private final ActivityStackSupervisor mSupervisor;
- protected static TaskRecord createTask(ActivityStackSupervisor supervisor,
- ComponentName component, int flags, int taskId, ActivityStack stack) {
- final ActivityInfo aInfo = new ActivityInfo();
- aInfo.applicationInfo = new ApplicationInfo();
- aInfo.applicationInfo.packageName = component.getPackageName();
+ private ComponentName mComponent;
+ private String mPackage;
+ private int mFlags = 0;
+ private int mTaskId = 0;
- Intent intent = new Intent();
- intent.setComponent(component);
- intent.setFlags(flags);
+ private ActivityStack mStack;
- final TaskRecord task = new TaskRecord(supervisor.mService, taskId, aInfo,
- intent /*intent*/, null /*_taskDescription*/);
- supervisor.setFocusStackUnchecked("test", stack);
- stack.addTask(task, true, "creating test task");
- task.setStack(stack);
- task.setWindowContainerController(mock(TaskWindowContainerController.class));
+ TaskBuilder(ActivityStackSupervisor supervisor) {
+ mSupervisor = supervisor;
+ }
- return task;
+ TaskBuilder setComponent(ComponentName component) {
+ mComponent = component;
+ return this;
+ }
+
+ TaskBuilder setPackage(String packageName) {
+ mPackage = packageName;
+ return this;
+ }
+
+ TaskBuilder setFlags(int flags) {
+ mFlags = flags;
+ return this;
+ }
+
+ TaskBuilder setTaskId(int taskId) {
+ mTaskId = taskId;
+ return this;
+ }
+
+ TaskBuilder setStack(ActivityStack stack) {
+ mStack = stack;
+ return this;
+ }
+
+ TaskRecord build() {
+ if (mStack == null) {
+ mStack = mSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ }
+
+ final ActivityInfo aInfo = new ActivityInfo();
+ aInfo.applicationInfo = new ApplicationInfo();
+ aInfo.applicationInfo.packageName = mPackage;
+
+ Intent intent = new Intent();
+ intent.setComponent(mComponent);
+ intent.setFlags(mFlags);
+
+ final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo,
+ intent /*intent*/, null /*_taskDescription*/);
+ mSupervisor.setFocusStackUnchecked("test", mStack);
+ mStack.addTask(task, true, "creating test task");
+ task.setStack(mStack);
+ task.setWindowContainerController(mock(TaskWindowContainerController.class));
+
+ return task;
+ }
}
/**
@@ -268,20 +381,21 @@
return service;
}
- protected interface ActivityStackReporter {
- int onActivityRemovedFromStackInvocationCount();
- }
-
/**
* Overrided of {@link ActivityStack} that tracks test metrics, such as the number of times a
* method is called. Note that its functionality depends on the implementations of the
* construction arguments.
*/
protected static class TestActivityStack<T extends StackWindowController>
- extends ActivityStack<T> implements ActivityStackReporter {
+ extends ActivityStack<T> {
private int mOnActivityRemovedFromStackCount = 0;
private T mContainerController;
+ static final int IS_TRANSLUCENT_UNSET = 0;
+ static final int IS_TRANSLUCENT_FALSE = 1;
+ static final int IS_TRANSLUCENT_TRUE = 2;
+ private int mIsTranslucent = IS_TRANSLUCENT_UNSET;
+
TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
int windowingMode, int activityType, boolean onTop) {
super(display, stackId, supervisor, windowingMode, activityType, onTop);
@@ -294,8 +408,7 @@
}
// Returns the number of times {@link #onActivityRemovedFromStack} has been called
- @Override
- public int onActivityRemovedFromStackInvocationCount() {
+ int onActivityRemovedFromStackInvocationCount() {
return mOnActivityRemovedFromStackCount;
}
@@ -309,5 +422,22 @@
T getWindowContainerController() {
return mContainerController;
}
+
+ void setIsTranslucent(boolean isTranslucent) {
+ mIsTranslucent = isTranslucent ? IS_TRANSLUCENT_TRUE : IS_TRANSLUCENT_FALSE;
+ }
+
+ @Override
+ boolean isStackTranslucent(ActivityRecord starting) {
+ switch (mIsTranslucent) {
+ case IS_TRANSLUCENT_TRUE:
+ return true;
+ case IS_TRANSLUCENT_FALSE:
+ return false;
+ case IS_TRANSLUCENT_UNSET:
+ default:
+ return super.isStackTranslucent(starting);
+ }
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java b/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
new file mode 100644
index 0000000..bec46db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.graphics.Bitmap.Config.ARGB_8888;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.view.IWindowManager;
+
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Note: Currently, we only support fetching the screenshot for the current application, so the
+ * screenshot checks are hardcoded accordingly.
+ *
+ * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AssistDataRequesterTest extends ActivityTestsBase {
+
+ private static final String TAG = AssistDataRequesterTest.class.getSimpleName();
+
+ private static final boolean CURRENT_ACTIVITY_ASSIST_ALLOWED = true;
+ private static final boolean CALLER_ASSIST_STRUCTURE_ALLOWED = true;
+ private static final boolean CALLER_ASSIST_SCREENSHOT_ALLOWED = true;
+ private static final boolean FETCH_DATA = true;
+ private static final boolean FETCH_SCREENSHOTS = true;
+
+ private static final int TEST_UID = 0;
+ private static final String TEST_PACKAGE = "";
+
+ private Context mContext;
+ private AssistDataRequester mDataRequester;
+ private Callbacks mCallbacks;
+ private Object mCallbacksLock;
+ private Handler mHandler;
+ private IActivityManager mAm;
+ private IWindowManager mWm;
+ private AppOpsManager mAppOpsManager;
+
+ /**
+ * The requests to fetch assist data are done incrementally from the text thread, and we
+ * immediately post onto the main thread handler below, which would immediately make the
+ * callback and decrement the pending counts. In order to assert the pending counts, we defer
+ * the callbacks on the test-side until after we flip the gate, after which we can drain the
+ * main thread handler and make assertions on the actual callbacks
+ */
+ private CountDownLatch mGate;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mAm = mock(IActivityManager.class);
+ mWm = mock(IWindowManager.class);
+ mAppOpsManager = mock(AppOpsManager.class);
+ mContext = InstrumentationRegistry.getContext();
+ mHandler = new Handler(Looper.getMainLooper());
+ mCallbacksLock = new Object();
+ mCallbacks = new Callbacks();
+ mDataRequester = new AssistDataRequester(mContext, mAm, mWm, mAppOpsManager, mCallbacks,
+ mCallbacksLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
+
+ // Gate the continuation of the assist data callbacks until we are ready within the tests
+ mGate = new CountDownLatch(1);
+ doAnswer(invocation -> {
+ mHandler.post(() -> {
+ try {
+ mGate.await(10, TimeUnit.SECONDS);
+ mDataRequester.onHandleAssistData(new Bundle());
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Failed to wait", e);
+ }
+ });
+ return true;
+ }).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(), anyBoolean(),
+ anyBoolean());
+ doAnswer(invocation -> {
+ mHandler.post(() -> {
+ try {
+ mGate.await(10, TimeUnit.SECONDS);
+ mDataRequester.onHandleAssistScreenshot(Bitmap.createBitmap(1, 1,
+ ARGB_8888));
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Failed to wait", e);
+ }
+ });
+ return true;
+ }).when(mWm).requestAssistScreenshot(any());
+ }
+
+ private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
+ boolean assistScreenshotAllowed) throws Exception {
+ doReturn(currentActivityAssistAllowed).when(mAm).isAssistDataAllowedOnCurrentActivity();
+ doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
+ .checkOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString());
+ doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
+ .checkOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString());
+ }
+
+ @Test
+ public void testRequestData() throws Exception {
+ setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+ CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+ mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+ TEST_UID, TEST_PACKAGE);
+ assertReceivedDataCount(5, 5, 1, 1);
+ }
+
+ @Test
+ public void testEmptyActivities_expectNoCallbacks() throws Exception {
+ setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+ CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+ mDataRequester.requestAssistData(createActivityList(0), FETCH_DATA, FETCH_SCREENSHOTS,
+ TEST_UID, TEST_PACKAGE);
+ assertReceivedDataCount(0, 0, 0, 0);
+ }
+
+ @Test
+ public void testCurrentAppDisallow_expectNoCallbacks() throws Exception {
+ setupMocks(!CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+ CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+ mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+ TEST_UID, TEST_PACKAGE);
+ assertReceivedDataCount(0, 0, 0, 0);
+ }
+
+ @Test
+ public void testProcessPendingData() throws Exception {
+ setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+ CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+ mCallbacks.canHandleReceivedData = false;
+ mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+ TEST_UID, TEST_PACKAGE);
+ assertTrue(mDataRequester.getPendingDataCount() == 5);
+ assertTrue(mDataRequester.getPendingScreenshotCount() == 1);
+ mGate.countDown();
+ waitForIdle(mHandler);
+
+ // Callbacks still not ready to receive, but all pending data is received
+ assertTrue(mDataRequester.getPendingDataCount() == 0);
+ assertTrue(mDataRequester.getPendingScreenshotCount() == 0);
+ assertTrue(mCallbacks.receivedData.isEmpty());
+ assertTrue(mCallbacks.receivedScreenshots.isEmpty());
+
+ mCallbacks.canHandleReceivedData = true;
+ mDataRequester.processPendingAssistData();
+ assertTrue(mCallbacks.receivedData.size() == 5);
+ assertTrue(mCallbacks.receivedScreenshots.size() == 1);
+ }
+
+ @Test
+ public void testNoFetchData_expectNoCallbacks() throws Exception {
+ setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+ CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+ mDataRequester.requestAssistData(createActivityList(5), !FETCH_DATA, FETCH_SCREENSHOTS,
+ TEST_UID, TEST_PACKAGE);
+ assertReceivedDataCount(0, 0, 0, 0);
+ }
+
+ @Test
+ public void testDisallowAssistStructure_expectNullDataCallbacks() throws Exception {
+ setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED,
+ CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+ mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+ TEST_UID, TEST_PACKAGE);
+ // Expect a single null data when the appops is denied
+ assertReceivedDataCount(0, 1, 0, 0);
+ }
+
+ @Test
+ public void testDisallowAssistContextExtras_expectNullDataCallbacks() throws Exception {
+ setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+ CALLER_ASSIST_SCREENSHOT_ALLOWED);
+ doReturn(false).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(),
+ anyBoolean(), anyBoolean());
+
+ mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+ TEST_UID, TEST_PACKAGE);
+ // Expect a single null data when requestAssistContextExtras() fails
+ assertReceivedDataCount(0, 1, 0, 0);
+ }
+
+ @Test
+ public void testNoFetchScreenshots_expectNoScreenshotCallbacks() throws Exception {
+ setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+ CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+ mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, !FETCH_SCREENSHOTS,
+ TEST_UID, TEST_PACKAGE);
+ assertReceivedDataCount(5, 5, 0, 0);
+ }
+
+ @Test
+ public void testDisallowAssistScreenshot_expectNullScreenshotCallback() throws Exception {
+ setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+ !CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+ mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+ TEST_UID, TEST_PACKAGE);
+ // Expect a single null screenshot when the appops is denied
+ assertReceivedDataCount(5, 5, 0, 1);
+ }
+
+ private void assertReceivedDataCount(int numPendingData, int numReceivedData,
+ int numPendingScreenshots, int numReceivedScreenshots) throws Exception {
+ assertTrue("Expected " + numPendingData + " pending data, got "
+ + mDataRequester.getPendingDataCount(),
+ mDataRequester.getPendingDataCount() == numPendingData);
+ assertTrue("Expected " + numPendingScreenshots + " pending screenshots, got "
+ + mDataRequester.getPendingScreenshotCount(),
+ mDataRequester.getPendingScreenshotCount() == numPendingScreenshots);
+ mGate.countDown();
+ waitForIdle(mHandler);
+ assertTrue("Expected " + numReceivedData + " data, received "
+ + mCallbacks.receivedData.size(),
+ mCallbacks.receivedData.size() == numReceivedData);
+ assertTrue("Expected " + numReceivedScreenshots + " screenshots, received "
+ + mCallbacks.receivedScreenshots.size(),
+ mCallbacks.receivedScreenshots.size() == numReceivedScreenshots);
+ }
+
+ private List<IBinder> createActivityList(int size) {
+ ArrayList<IBinder> activities = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ activities.add(mock(IBinder.class));
+ }
+ return activities;
+ }
+
+ public void waitForIdle(Handler h) throws Exception {
+ if (Looper.myLooper() == h.getLooper()) {
+ throw new RuntimeException("This method can not be called from the waiting looper");
+ }
+ CountDownLatch latch = new CountDownLatch(1);
+ h.post(() -> latch.countDown());
+ latch.await(2, TimeUnit.SECONDS);
+ }
+
+ private static class Callbacks implements AssistDataRequesterCallbacks {
+
+ boolean canHandleReceivedData = true;
+ ArrayList<Bundle> receivedData = new ArrayList<>();
+ ArrayList<Bitmap> receivedScreenshots = new ArrayList<>();
+
+ @Override
+ public boolean canHandleReceivedAssistDataLocked() {
+ return canHandleReceivedData;
+ }
+
+ @Override
+ public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+ receivedData.add(data);
+ }
+
+ @Override
+ public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+ receivedScreenshots.add(screenshot);
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java
new file mode 100644
index 0000000..62fa764
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.doAnswer;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP;
+
+/**
+ * Tests for exercising resizing bounds due to activity options.
+ *
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.am.LaunchingActivityPositionerTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LaunchingActivityPositionerTests extends ActivityTestsBase {
+ private LaunchingActivityPositioner mPositioner;
+ private ActivityManagerService mService;
+ private ActivityStack mStack;
+ private TaskRecord mTask;
+ private ActivityRecord mActivity;
+
+ private Rect mCurrent;
+ private Rect mResult;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mService = createActivityManagerService();
+ mPositioner = new LaunchingActivityPositioner(mService.mStackSupervisor);
+ mCurrent = new Rect();
+ mResult = new Rect();
+
+
+ mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+ mActivity = new ActivityBuilder(mService).setTask(mTask).build();
+ }
+
+
+ @Test
+ public void testSkippedInvocations() throws Exception {
+ // No specified activity should be ignored
+ assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+ null /*activity*/, null /*source*/, null /*options*/, mCurrent, mResult));
+
+ // No specified activity options should be ignored
+ assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+ mActivity, null /*source*/, null /*options*/, mCurrent, mResult));
+
+ // launch bounds specified should be ignored.
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+ mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+ // Non-resizeable records should be ignored
+ mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+ assertFalse(mActivity.isResizeable());
+ assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+ mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+ // make record resizeable
+ mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+ assertTrue(mActivity.isResizeable());
+
+ assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+ mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+ // Does not support freeform
+ mService.mSupportsFreeformWindowManagement = false;
+ assertFalse(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options));
+ assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+ mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+ mService.mSupportsFreeformWindowManagement = true;
+ options.setLaunchBounds(new Rect());
+ assertTrue(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options));
+
+ // Invalid bounds
+ assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+ mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+ options.setLaunchBounds(new Rect(0, 0, -1, -1));
+ assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+ mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+ // Valid bounds should cause the positioner to be applied.
+ options.setLaunchBounds(new Rect(0, 0, 100, 100));
+ assertEquals(RESULT_DONE, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+ mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+ }
+
+ @Test
+ public void testBoundsExtraction() throws Exception {
+ // Make activity resizeable and enable freeform mode.
+ mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+ mService.mSupportsFreeformWindowManagement = true;
+
+ ActivityOptions options = ActivityOptions.makeBasic();
+ final Rect proposedBounds = new Rect(20, 30, 45, 40);
+ options.setLaunchBounds(proposedBounds);
+
+ assertEquals(RESULT_DONE, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+ mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+ assertEquals(mResult, proposedBounds);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java
new file mode 100644
index 0000000..0715174
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.WindowLayout;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for exercising {@link LaunchingBoundsController}.
+ *
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.am.LaunchingBoundsControllerTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LaunchingBoundsControllerTests extends ActivityTestsBase {
+ private LaunchingBoundsController mController;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mController = new LaunchingBoundsController();
+ }
+
+ /**
+ * Makes sure positioners get values passed to controller.
+ */
+ @Test
+ public void testArgumentPropagation() {
+ final ActivityManagerService service = createActivityManagerService();
+ final LaunchingBoundsPositioner positioner = mock(LaunchingBoundsPositioner.class);
+ mController.registerPositioner(positioner);
+
+ final ActivityRecord record = new ActivityBuilder(service).build();
+ final ActivityRecord source = new ActivityBuilder(service).build();
+ final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0);
+ final ActivityOptions options = mock(ActivityOptions.class);
+
+ mController.calculateBounds(record.getTask(), layout, record, source, options, new Rect());
+ verify(positioner, times(1)).onCalculateBounds(eq(record.getTask()), eq(layout), eq(record),
+ eq(source), eq(options), any(), any());
+ }
+
+ /**
+ * Ensures positioners further down the chain are not called when RESULT_DONE is returned.
+ */
+ @Test
+ public void testEarlyExit() {
+ final LaunchingBoundsPositioner ignoredPositioner = mock(LaunchingBoundsPositioner.class);
+ final LaunchingBoundsPositioner earlyExitPositioner =
+ (task, layout, activity, source, options, current, result) -> RESULT_DONE;
+
+ mController.registerPositioner(ignoredPositioner);
+ mController.registerPositioner(earlyExitPositioner);
+
+ mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+ null /*source*/, null /*options*/, new Rect());
+ verify(ignoredPositioner, never()).onCalculateBounds(any(), any(), any(), any(), any(),
+ any(), any());
+ }
+
+ /**
+ * Ensures that positioners are called in the correct order.
+ */
+ @Test
+ public void testRegistration() {
+ LaunchingBoundsPositioner earlyExitPositioner =
+ new InstrumentedPositioner(RESULT_DONE, new Rect());
+
+ final LaunchingBoundsPositioner firstPositioner = spy(earlyExitPositioner);
+
+ mController.registerPositioner(firstPositioner);
+
+ mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+ null /*source*/, null /*options*/, new Rect());
+ verify(firstPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(),
+ any(), any());
+
+ final LaunchingBoundsPositioner secondPositioner = spy(earlyExitPositioner);
+
+ mController.registerPositioner(secondPositioner);
+
+ mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+ null /*source*/, null /*options*/, new Rect());
+ verify(firstPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(),
+ any(), any());
+ verify(secondPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(),
+ any(), any());
+ }
+
+ /**
+ * Makes sure positioners further down the registration chain are called.
+ */
+ @Test
+ public void testPassThrough() {
+ final LaunchingBoundsPositioner positioner1 = mock(LaunchingBoundsPositioner.class);
+ final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE,
+ new Rect (0, 0, 30, 20));
+
+ mController.registerPositioner(positioner1);
+ mController.registerPositioner(positioner2);
+
+ mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+ null /*source*/, null /*options*/, new Rect());
+
+ verify(positioner1, times(1)).onCalculateBounds(any(), any(), any(), any(), any(),
+ eq(positioner2.getLaunchBounds()), any());
+ }
+
+ /**
+ * Ensures skipped results are not propagated.
+ */
+ @Test
+ public void testSkip() {
+ final InstrumentedPositioner positioner1 =
+ new InstrumentedPositioner(RESULT_SKIP, new Rect(0, 0, 10, 10));
+
+
+ final InstrumentedPositioner positioner2 =
+ new InstrumentedPositioner(RESULT_CONTINUE, new Rect(0, 0, 20, 30));
+
+ mController.registerPositioner(positioner1);
+ mController.registerPositioner(positioner2);
+
+ final Rect resultBounds = new Rect();
+
+ mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+ null /*source*/, null /*options*/, resultBounds);
+
+ assertEquals(resultBounds, positioner2.getLaunchBounds());
+ }
+
+ public static class InstrumentedPositioner implements LaunchingBoundsPositioner {
+ private int mReturnVal;
+ private Rect mBounds;
+ InstrumentedPositioner(int returnVal, Rect bounds) {
+ mReturnVal = returnVal;
+ mBounds = bounds;
+ }
+
+ @Override
+ public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout,
+ ActivityRecord activity, ActivityRecord source,
+ ActivityOptions options, Rect current, Rect result) {
+ result.set(mBounds);
+ return mReturnVal;
+ }
+
+ Rect getLaunchBounds() {
+ return mBounds;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java
similarity index 77%
rename from services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java
rename to services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java
index e6d6831..01e2da6 100644
--- a/services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java
@@ -17,15 +17,12 @@
package com.android.server.am;
import android.content.ComponentName;
-import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.WindowLayout;
-import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
-import android.view.Display;
import android.view.Gravity;
import org.junit.runner.RunWith;
import org.junit.Before;
@@ -33,10 +30,11 @@
import org.mockito.invocation.InvocationOnMock;
-import java.util.ArrayList;
-
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -48,17 +46,12 @@
* Tests for exercising resizing bounds.
*
* Build/Install/Run:
- * bit FrameworksServicesTests:com.android.server.am.LaunchBoundsTests
+ * bit FrameworksServicesTests:com.android.server.am.LaunchingTaskPositionerTests
*/
@MediumTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class LaunchBoundsTests extends ActivityTestsBase {
- private final ComponentName testActivityComponent =
- ComponentName.unflattenFromString("com.foo/.BarActivity");
- private final ComponentName testActivityComponent2 =
- ComponentName.unflattenFromString("com.foo/.BarActivity2");
-
+public class LaunchingTaskPositionerTests extends ActivityTestsBase {
private final static int STACK_WIDTH = 100;
private final static int STACK_HEIGHT = 200;
@@ -68,6 +61,11 @@
private ActivityStack mStack;
private TaskRecord mTask;
+ private LaunchingTaskPositioner mPositioner;
+
+ private Rect mCurrent;
+ private Rect mResult;
+
@Before
@Override
public void setUp() throws Exception {
@@ -80,7 +78,12 @@
// We must create the task after resizing to make sure it does not inherit the stack
// dimensions on resize.
- mTask = createTask(mService.mStackSupervisor, testActivityComponent, mStack);
+ mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+
+ mPositioner = new LaunchingTaskPositioner();
+
+ mResult = new Rect();
+ mCurrent = new Rect();
}
/**
@@ -101,12 +104,9 @@
*/
@Test
public void testLaunchNoWindowLayout() throws Exception {
- final Rect expectedTaskBounds = getDefaultBounds(Gravity.NO_GRAVITY);
-
- mStack.layoutTaskInStack(mTask, null);
-
- // We expect the task to be placed in the middle of the screen with margins applied.
- assertEquals(mTask.mBounds, expectedTaskBounds);
+ assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask, null /*layout*/,
+ null /*record*/, null /*source*/, null /*options*/, mCurrent, mResult));
+ assertEquals(getDefaultBounds(Gravity.NO_GRAVITY), mResult);
}
/**
@@ -116,11 +116,10 @@
*/
@Test
public void testlaunchEmptyWindowLayout() throws Exception {
- final Rect expectedTaskBounds = getDefaultBounds(Gravity.NO_GRAVITY);
-
- WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0);
- mStack.layoutTaskInStack(mTask, layout);
- assertEquals(mTask.mBounds, expectedTaskBounds);
+ assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask,
+ new WindowLayout(0, 0, 0, 0, Gravity.NO_GRAVITY, 0, 0), null /*activity*/,
+ null /*source*/, null /*options*/, mCurrent, mResult));
+ assertEquals(mResult, getDefaultBounds(Gravity.NO_GRAVITY));
}
/**
@@ -149,9 +148,15 @@
}
private void testGravity(int gravity) {
- final WindowLayout gravityLayout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0);
- mStack.layoutTaskInStack(mTask, gravityLayout);
- assertEquals(mTask.mBounds, getDefaultBounds(gravity));
+ try {
+ assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask,
+ new WindowLayout(0, 0, 0, 0, gravity, 0, 0), null /*activity*/,
+ null /*source*/, null /*options*/, mCurrent, mResult));
+ assertEquals(mResult, getDefaultBounds(gravity));
+ } finally {
+ mCurrent.setEmpty();
+ mResult.setEmpty();
+ }
}
/**
@@ -174,7 +179,7 @@
final WindowLayout layout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0);
// layout first task
- mStack.layoutTaskInStack(mTask, layout /*windowLayout*/);
+ mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(mTask, layout);
// Second task will be laid out on top of the first so starting bounds is the same.
final Rect expectedBounds = new Rect(mTask.mBounds);
@@ -185,14 +190,15 @@
// wrap with try/finally to ensure cleanup of activity/stack.
try {
// empty tasks are ignored in conflicts
- activity = createActivity(mService, testActivityComponent, mTask);
+ activity = new ActivityBuilder(mService).setTask(mTask).build();
// Create secondary task
- secondTask = createTask(mService.mStackSupervisor, testActivityComponent,
- mStack);
+ secondTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
// layout second task
- mStack.layoutTaskInStack(secondTask, layout /*windowLayout*/);
+ assertEquals(RESULT_CONTINUE,
+ mPositioner.onCalculateBounds(secondTask, layout, null /*activity*/,
+ null /*source*/, null /*options*/, mCurrent, mResult));
if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)
|| (gravity & (Gravity.BOTTOM | Gravity.RIGHT))
@@ -207,7 +213,7 @@
LaunchingTaskPositioner.getVerticalStep(mStack.mBounds));
}
- assertEquals(secondTask.mBounds, expectedBounds);
+ assertEquals(mResult, expectedBounds);
} finally {
// Remove task and activity to prevent influencing future tests
if (activity != null) {
diff --git a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
index 4c1d3e9..1adfb67 100644
--- a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
@@ -19,6 +19,16 @@
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
+import static android.app.StatusBarManager.DISABLE2_MASK;
+import static android.app.StatusBarManager.DISABLE2_NONE;
+import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
+import static android.app.StatusBarManager.DISABLE_HOME;
+import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
+import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
import static android.os.Process.SYSTEM_UID;
import static com.android.server.am.LockTaskController.STATUS_BAR_MASK_LOCKED;
@@ -29,6 +39,7 @@
import static org.mockito.Mockito.*;
import android.app.StatusBarManager;
+import android.app.admin.DevicePolicyManager;
import android.app.admin.IDevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
@@ -42,6 +53,7 @@
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
+import android.util.Pair;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
@@ -136,10 +148,10 @@
// THEN the lock task mode state should be LOCKED
assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
// THEN the task should be locked
- assertTrue(mLockTaskController.checkLockedTask(tr));
+ assertTrue(mLockTaskController.isTaskLocked(tr));
// THEN lock task mode should be started
- verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED);
+ verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
}
@Test
@@ -155,11 +167,11 @@
// THEN the lock task mode state should be LOCKED
assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
// THEN neither of the tasks should be able to move to back of stack
- assertTrue(mLockTaskController.checkLockedTask(tr1));
- assertTrue(mLockTaskController.checkLockedTask(tr2));
+ assertTrue(mLockTaskController.isTaskLocked(tr1));
+ assertTrue(mLockTaskController.isTaskLocked(tr2));
// THEN lock task mode should be started
- verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED);
+ verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
}
@Test
@@ -185,10 +197,10 @@
// THEN the lock task mode state should be PINNED
assertEquals(LOCK_TASK_MODE_PINNED, mLockTaskController.getLockTaskModeState());
// THEN the task should be locked
- assertTrue(mLockTaskController.checkLockedTask(tr));
+ assertTrue(mLockTaskController.isTaskLocked(tr));
// THEN lock task mode should be started
- verifyLockTaskStarted(STATUS_BAR_MASK_PINNED);
+ verifyLockTaskStarted(STATUS_BAR_MASK_PINNED, DISABLE2_NONE);
// THEN screen pinning toast should be shown
verify(mLockTaskNotify).showPinningStartToast();
}
@@ -226,36 +238,36 @@
mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
// WHEN the same caller calls stopLockTaskMode
- mLockTaskController.stopLockTaskMode(false, TEST_UID);
+ mLockTaskController.stopLockTaskMode(tr, false, TEST_UID);
// THEN the lock task mode should be NONE
assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
// THEN the task should no longer be locked
- assertFalse(mLockTaskController.checkLockedTask(tr));
+ assertFalse(mLockTaskController.isTaskLocked(tr));
// THEN lock task mode should have been finished
verifyLockTaskStopped(times(1));
}
@Test(expected = SecurityException.class)
- public void testStopLockTaskMode_DifferentCaller() throws Exception {
+ public void testStopLockTaskMode_differentCaller() throws Exception {
// GIVEN one task record with whitelisted auth that is in lock task mode
TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
// WHEN a different caller calls stopLockTaskMode
- mLockTaskController.stopLockTaskMode(false, TEST_UID + 1);
+ mLockTaskController.stopLockTaskMode(tr, false, TEST_UID + 1);
// THEN security exception should be thrown, because different caller tried to unlock
}
@Test
- public void testStopLockTaskMode_SystemCaller() throws Exception {
+ public void testStopLockTaskMode_systemCaller() throws Exception {
// GIVEN one task record with whitelisted auth that is in lock task mode
TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
// WHEN system calls stopLockTaskMode
- mLockTaskController.stopLockTaskMode(true, SYSTEM_UID);
+ mLockTaskController.stopLockTaskMode(tr, true, SYSTEM_UID);
// THEN lock task mode should still be active
assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
@@ -270,19 +282,40 @@
mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
// WHEN calling stopLockTaskMode
- mLockTaskController.stopLockTaskMode(false, TEST_UID);
+ mLockTaskController.stopLockTaskMode(tr2, false, TEST_UID);
// THEN the lock task mode should still be active
assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
// THEN the first task should still be locked
- assertTrue(mLockTaskController.checkLockedTask(tr1));
+ assertTrue(mLockTaskController.isTaskLocked(tr1));
// THEN the top task should no longer be locked
- assertFalse(mLockTaskController.checkLockedTask(tr2));
+ assertFalse(mLockTaskController.isTaskLocked(tr2));
// THEN lock task mode should not have been finished
verifyLockTaskStopped(never());
}
@Test
+ public void testStopLockTaskMode_rootTask() throws Exception {
+ // GIVEN two task records with whitelisted auth that is in lock task mode
+ TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+ TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+ mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
+ mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
+
+ // WHEN calling stopLockTaskMode on the root task
+ mLockTaskController.stopLockTaskMode(tr1, false, TEST_UID);
+
+ // THEN the lock task mode should be inactive
+ assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
+ // THEN the first task should no longer be locked
+ assertFalse(mLockTaskController.isTaskLocked(tr1));
+ // THEN the top task should no longer be locked
+ assertFalse(mLockTaskController.isTaskLocked(tr2));
+ // THEN lock task mode should be finished
+ verifyLockTaskStopped(times(1));
+ }
+
+ @Test
public void testStopLockTaskMode_pinned() throws Exception {
// GIVEN one task records that is in pinned mode
TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE);
@@ -291,13 +324,16 @@
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 1);
+ // reset invocation counter
+ reset(mStatusBarService);
+
// WHEN calling stopLockTask
- mLockTaskController.stopLockTaskMode(true, SYSTEM_UID);
+ mLockTaskController.stopLockTaskMode(null, true, SYSTEM_UID);
// THEN the lock task mode should no longer be active
assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
// THEN the task should no longer be locked
- assertFalse(mLockTaskController.checkLockedTask(tr));
+ assertFalse(mLockTaskController.isTaskLocked(tr));
// THEN lock task mode should have been finished
verifyLockTaskStopped(times(1));
// THEN the keyguard should be shown
@@ -307,6 +343,27 @@
}
@Test
+ public void testClearLockedTasks() throws Exception {
+ // GIVEN two task records with whitelisted auth that is in lock task mode
+ TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+ TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+ mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
+ mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
+
+ // WHEN calling stopLockTaskMode on the root task
+ mLockTaskController.clearLockedTasks("testClearLockedTasks");
+
+ // THEN the lock task mode should be inactive
+ assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
+ // THEN the first task should no longer be locked
+ assertFalse(mLockTaskController.isTaskLocked(tr1));
+ // THEN the top task should no longer be locked
+ assertFalse(mLockTaskController.isTaskLocked(tr2));
+ // THEN lock task mode should be finished
+ verifyLockTaskStopped(times(1));
+ }
+
+ @Test
public void testUpdateLockTaskPackages() throws Exception {
String[] whitelist1 = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2};
String[] whitelist2 = {TEST_PACKAGE_NAME};
@@ -352,9 +409,9 @@
mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
- assertTrue(mLockTaskController.checkLockedTask(tr1));
- assertTrue(mLockTaskController.checkLockedTask(tr2));
- verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED);
+ assertTrue(mLockTaskController.isTaskLocked(tr1));
+ assertTrue(mLockTaskController.isTaskLocked(tr2));
+ verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
// WHEN removing one package from whitelist
whitelist = new String[] {TEST_PACKAGE_NAME};
@@ -362,11 +419,11 @@
// THEN the task running that package should be stopped
verify(tr2).performClearTaskLocked();
- assertFalse(mLockTaskController.checkLockedTask(tr2));
+ assertFalse(mLockTaskController.isTaskLocked(tr2));
// THEN the other task should remain locked
assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
- assertTrue(mLockTaskController.checkLockedTask(tr1));
- verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED);
+ assertTrue(mLockTaskController.isTaskLocked(tr1));
+ verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
// WHEN removing the last package from whitelist
whitelist = new String[] {};
@@ -374,11 +431,136 @@
// THEN the last task should be cleared, and the system should quit LockTask mode
verify(tr1).performClearTaskLocked();
- assertFalse(mLockTaskController.checkLockedTask(tr1));
+ assertFalse(mLockTaskController.isTaskLocked(tr1));
assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
verifyLockTaskStopped(times(1));
}
+ @Test
+ public void testUpdateLockTaskFeatures() throws Exception {
+ // GIVEN a locked task
+ TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+ mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+ // THEN lock task mode should be started with default status bar masks
+ verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
+
+ // reset invocation counter
+ reset(mStatusBarService);
+
+ // WHEN home button is enabled for lock task mode
+ mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_HOME);
+
+ // THEN status bar should be updated to reflect this change
+ int expectedFlags = STATUS_BAR_MASK_LOCKED
+ & ~DISABLE_HOME;
+ int expectedFlags2 = DISABLE2_MASK;
+ verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class),
+ eq(mContext.getPackageName()));
+ verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class),
+ eq(mContext.getPackageName()));
+
+ // reset invocation counter
+ reset(mStatusBarService);
+
+ // WHEN notifications are enabled for lock task mode
+ mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NOTIFICATIONS);
+
+ // THEN status bar should be updated to reflect this change
+ expectedFlags = STATUS_BAR_MASK_LOCKED
+ & ~DISABLE_NOTIFICATION_ICONS
+ & ~DISABLE_NOTIFICATION_ALERTS;
+ expectedFlags2 = DISABLE2_MASK
+ & ~DISABLE2_NOTIFICATION_SHADE;
+ verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class),
+ eq(mContext.getPackageName()));
+ verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class),
+ eq(mContext.getPackageName()));
+ }
+
+ @Test
+ public void testUpdateLockTaskFeatures_differentUser() throws Exception {
+ // GIVEN a locked task
+ TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+ mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+ // THEN lock task mode should be started with default status bar masks
+ verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
+
+ // reset invocation counter
+ reset(mStatusBarService);
+
+ // WHEN home button is enabled for lock task mode for another user
+ mLockTaskController.updateLockTaskFeatures(TEST_USER_ID + 1, LOCK_TASK_FEATURE_HOME);
+
+ // THEN status bar shouldn't change
+ verify(mStatusBarService, never()).disable(anyInt(), any(IBinder.class),
+ eq(mContext.getPackageName()));
+ verify(mStatusBarService, never()).disable2(anyInt(), any(IBinder.class),
+ eq(mContext.getPackageName()));
+ }
+
+ @Test
+ public void testUpdateLockTaskFeatures_keyguard() throws Exception {
+ // GIVEN a locked task
+ TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+ mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+ // THEN keyguard should be disabled
+ verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString());
+
+ // WHEN keyguard is enabled for lock task mode
+ mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_KEYGUARD);
+
+ // THEN keyguard should be enabled
+ verify(mWindowManager).reenableKeyguard(any(IBinder.class));
+
+ // WHEN keyguard is disabled again for lock task mode
+ mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NONE);
+
+ // THEN keyguard should be disabled
+ verify(mWindowManager, times(2)).disableKeyguard(any(IBinder.class), anyString());
+ }
+
+ @Test
+ public void testGetStatusBarDisableFlags() {
+ // Note that we don't enumerate all StatusBarManager flags, but only choose a subset to test
+
+ // WHEN nothing is enabled
+ Pair<Integer, Integer> flags = mLockTaskController.getStatusBarDisableFlags(
+ LOCK_TASK_FEATURE_NONE);
+ // THEN unsupported feature flags should still be untouched
+ assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0);
+ // THEN everything else should be disabled
+ assertTrue((StatusBarManager.DISABLE_CLOCK & flags.first) != 0);
+ assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
+
+ // WHEN only home button is enabled
+ flags = mLockTaskController.getStatusBarDisableFlags(
+ LOCK_TASK_FEATURE_HOME);
+ // THEN unsupported feature flags should still be untouched
+ assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0);
+ // THEN home button should indeed be enabled
+ assertTrue((StatusBarManager.DISABLE_HOME & flags.first) == 0);
+ // THEN other feature flags should remain disabled
+ assertTrue((StatusBarManager.DISABLE2_NOTIFICATION_SHADE & flags.second) != 0);
+
+ // WHEN only global actions menu and notifications are enabled
+ flags = mLockTaskController.getStatusBarDisableFlags(
+ DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+ | DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS);
+ // THEN unsupported feature flags should still be untouched
+ assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0);
+ // THEN notifications should be enabled
+ assertTrue((StatusBarManager.DISABLE_NOTIFICATION_ICONS & flags.first) == 0);
+ assertTrue((StatusBarManager.DISABLE_NOTIFICATION_ALERTS & flags.first) == 0);
+ assertTrue((StatusBarManager.DISABLE2_NOTIFICATION_SHADE & flags.second) == 0);
+ // THEN global actions should be enabled
+ assertTrue((StatusBarManager.DISABLE2_GLOBAL_ACTIONS & flags.second) == 0);
+ // THEN quick settings should still be disabled
+ assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
+ }
+
private TaskRecord getTaskRecord(int lockTaskAuth) {
return getTaskRecord(TEST_PACKAGE_NAME, lockTaskAuth);
}
@@ -411,12 +593,14 @@
return tr;
}
- private void verifyLockTaskStarted(int statusBarMask) throws Exception {
+ private void verifyLockTaskStarted(int statusBarMask, int statusBarMask2) throws Exception {
// THEN the keyguard should have been disabled
verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString());
// THEN the status bar should have been disabled
verify(mStatusBarService).disable(eq(statusBarMask), any(IBinder.class),
eq(mContext.getPackageName()));
+ verify(mStatusBarService).disable2(eq(statusBarMask2), any(IBinder.class),
+ eq(mContext.getPackageName()));
// THEN the DO/PO should be informed about the operation
verify(mDevicePolicyManager).notifyLockTaskModeChanged(true, TEST_PACKAGE_NAME,
TEST_USER_ID);
@@ -428,6 +612,8 @@
// THEN the status bar should have been disabled
verify(mStatusBarService, mode).disable(eq(StatusBarManager.DISABLE_NONE),
any(IBinder.class), eq(mContext.getPackageName()));
+ verify(mStatusBarService, mode).disable2(eq(StatusBarManager.DISABLE2_NONE),
+ any(IBinder.class), eq(mContext.getPackageName()));
// THEN the DO/PO should be informed about the operation
verify(mDevicePolicyManager, mode).notifyLockTaskModeChanged(false, null, TEST_USER_ID);
}
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index e607228..f5ea60f 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -16,44 +16,55 @@
package com.android.server.am;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
+import static java.lang.Integer.MAX_VALUE;
+
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
-import android.os.Debug;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.MutableLong;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.server.am.RecentTasks.Callbacks;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -67,13 +78,17 @@
private static final int TEST_QUIET_USER_ID = 20;
private static final UserInfo DEFAULT_USER_INFO = new UserInfo();
private static final UserInfo QUIET_USER_INFO = new UserInfo();
+ private static final ComponentName MY_COMPONENT = new ComponentName(
+ RecentTasksTest.class.getPackage().getName(), RecentTasksTest.class.getName());
private static int LAST_TASK_ID = 1;
+ private static int INVALID_STACK_ID = 999;
private Context mContext = InstrumentationRegistry.getContext();
private ActivityManagerService mService;
private ActivityStack mStack;
private TestTaskPersister mTaskPersister;
private RecentTasks mRecentTasks;
+ private RunningTasks mRunningTasks;
private static ArrayList<TaskRecord> mTasks = new ArrayList<>();
private static ArrayList<TaskRecord> mSameDocumentTasks = new ArrayList<>();
@@ -91,6 +106,14 @@
}
@Override
+ Set<Integer> getProfileIds(int userId) {
+ Set<Integer> profileIds = new HashSet<>();
+ profileIds.add(TEST_USER_0_ID);
+ profileIds.add(TEST_QUIET_USER_ID);
+ return profileIds;
+ }
+
+ @Override
UserInfo getUserInfo(int userId) {
switch (userId) {
case TEST_USER_0_ID:
@@ -108,12 +131,12 @@
public void setUp() throws Exception {
super.setUp();
- mService = createActivityManagerService();
+ mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
+ mService = setupActivityManagerService(new MyTestActivityManagerService(mContext));
+ mRecentTasks = mService.getRecentTasks();
+ mRecentTasks.loadParametersFromResources(mContext.getResources());
mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
- mRecentTasks = new RecentTasks(mService, mTaskPersister, new TestUserController(mService));
- mRecentTasks.loadParametersFromResources(mContext.getResources());
mCallbacksRecorder = new CallbacksRecorder();
mRecentTasks.registerCallback(mCallbacksRecorder);
QUIET_USER_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE;
@@ -322,6 +345,104 @@
assertTrimmed(mTasks.get(0), mTasks.get(1));
}
+ @Test
+ public void testNotRecentsComponent_denyApiAccess() throws Exception {
+ doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
+ anyInt(), anyInt());
+
+ // Expect the following methods to fail due to recents component not being set
+ ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(
+ TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
+ testRecentTasksApis(false /* expectNoSecurityException */);
+ // Don't throw for the following tests
+ ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.DENY);
+ testGetTasksApis(false /* expectNoSecurityException */);
+ }
+
+ @Test
+ public void testRecentsComponent_allowApiAccessWithoutPermissions() throws Exception {
+ doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
+ anyInt(), anyInt());
+
+ // Set the recents component and ensure that the following calls do not fail
+ ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.GRANT);
+ testRecentTasksApis(true /* expectNoSecurityException */);
+ testGetTasksApis(true /* expectNoSecurityException */);
+ }
+
+ private void testRecentTasksApis(boolean expectCallable) {
+ assertSecurityException(expectCallable, () -> mService.removeStack(INVALID_STACK_ID));
+ assertSecurityException(expectCallable,
+ () -> mService.removeStacksInWindowingModes(new int[] {WINDOWING_MODE_UNDEFINED}));
+ assertSecurityException(expectCallable,
+ () -> mService.removeStacksWithActivityTypes(new int[] {ACTIVITY_TYPE_UNDEFINED}));
+ assertSecurityException(expectCallable, () -> mService.removeTask(0));
+ assertSecurityException(expectCallable,
+ () -> mService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true));
+ assertSecurityException(expectCallable,
+ () -> mService.moveTaskToStack(0, INVALID_STACK_ID, true));
+ assertSecurityException(expectCallable,
+ () -> mService.setTaskWindowingModeSplitScreenPrimary(0,
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect()));
+ assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true));
+ assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0));
+ assertSecurityException(expectCallable,
+ () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
+ assertSecurityException(expectCallable,
+ () -> mService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0));
+ assertSecurityException(expectCallable,
+ () -> mService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(),
+ new Rect()));
+ assertSecurityException(expectCallable,
+ () -> mService.resizePinnedStack(new Rect(), new Rect()));
+ assertSecurityException(expectCallable, () -> mService.getAllStackInfos());
+ assertSecurityException(expectCallable,
+ () -> mService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED));
+ assertSecurityException(expectCallable, () -> {
+ try {
+ mService.getFocusedStackInfo();
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ });
+ assertSecurityException(expectCallable,
+ () -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true));
+ assertSecurityException(expectCallable,
+ () -> mService.startActivityFromRecents(0, new Bundle()));
+ assertSecurityException(expectCallable,
+ () -> mService.getTaskSnapshot(0, true));
+ assertSecurityException(expectCallable, () -> {
+ try {
+ mService.registerTaskStackListener(null);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ });
+ assertSecurityException(expectCallable, () -> {
+ try {
+ mService.unregisterTaskStackListener(null);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ });
+ assertSecurityException(expectCallable, () -> mService.getTaskDescription(0));
+ assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
+ assertSecurityException(expectCallable, () -> mService.cancelTaskThumbnailTransition(0));
+ assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null, 0));
+ }
+
+ private void testGetTasksApis(boolean expectCallable) {
+ mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID);
+ mService.getTasks(MAX_VALUE);
+ if (expectCallable) {
+ assertTrue(((TestRecentTasks) mRecentTasks).mLastAllowed);
+ assertTrue(((TestRunningTasks) mRunningTasks).mLastAllowed);
+ } else {
+ assertFalse(((TestRecentTasks) mRecentTasks).mLastAllowed);
+ assertFalse(((TestRunningTasks) mRunningTasks).mLastAllowed);
+ }
+ }
+
private ComponentName createComponent(String className) {
return new ComponentName(mContext.getPackageName(), className);
}
@@ -335,8 +456,9 @@
}
private TaskRecord createTask(String className, int flags, int userId) {
- TaskRecord task = createTask(mService.mStackSupervisor, createComponent(className), flags,
- LAST_TASK_ID++, mStack);
+ TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
+ .setComponent(createComponent(className))
+ .setStack(mStack).setFlags(flags).setTaskId(LAST_TASK_ID++).build();
task.userId = userId;
task.touchActiveTime();
return task;
@@ -366,6 +488,55 @@
}
}
+ private void assertSecurityException(boolean expectCallable, Runnable runnable) {
+ boolean noSecurityException = true;
+ try {
+ runnable.run();
+ } catch (SecurityException se) {
+ noSecurityException = false;
+ } catch (Exception e) {
+ // We only care about SecurityExceptions, fall through here
+ e.printStackTrace();
+ }
+ if (noSecurityException != expectCallable) {
+ fail("Expected callable: " + expectCallable + " but got no security exception: "
+ + noSecurityException);
+ }
+ }
+
+ private class MyTestActivityManagerService extends TestActivityManagerService {
+ MyTestActivityManagerService(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected ActivityStackSupervisor createStackSupervisor() {
+ return new MyTestActivityStackSupervisor(this, mHandlerThread.getLooper());
+ }
+
+ @Override
+ protected RecentTasks createRecentTasks() {
+ return new TestRecentTasks(this, mTaskPersister, new TestUserController(this));
+ }
+
+ @Override
+ public boolean isUserRunning(int userId, int flags) {
+ return true;
+ }
+ }
+
+ private class MyTestActivityStackSupervisor extends TestActivityStackSupervisor {
+ public MyTestActivityStackSupervisor(ActivityManagerService service, Looper looper) {
+ super(service, looper);
+ }
+
+ @Override
+ RunningTasks createRunningTasks() {
+ mRunningTasks = new TestRunningTasks();
+ return mRunningTasks;
+ }
+ }
+
private static class CallbacksRecorder implements Callbacks {
ArrayList<TaskRecord> added = new ArrayList<>();
ArrayList<TaskRecord> trimmed = new ArrayList<>();
@@ -416,4 +587,61 @@
return super.restoreTasksForUserLocked(userId, preaddedTasks);
}
}
+
+ private static class TestRecentTasks extends RecentTasks {
+ static final int GRANT = 0;
+ static final int DENY = 1;
+ static final int DENY_THROW_SECURITY_EXCEPTION = 2;
+
+ private boolean mOverrideIsCallerRecents;
+ private int mIsCallerRecentsPolicy;
+ boolean mLastAllowed;
+
+ TestRecentTasks(ActivityManagerService service, TaskPersister taskPersister,
+ UserController userController) {
+ super(service, taskPersister, userController);
+ }
+
+ @Override
+ boolean isCallerRecents(int callingUid) {
+ if (mOverrideIsCallerRecents) {
+ switch (mIsCallerRecentsPolicy) {
+ case GRANT:
+ return true;
+ case DENY:
+ return false;
+ case DENY_THROW_SECURITY_EXCEPTION:
+ throw new SecurityException();
+ }
+ }
+ return super.isCallerRecents(callingUid);
+ }
+
+ void setIsCallerRecentsOverride(int policy) {
+ mOverrideIsCallerRecents = true;
+ mIsCallerRecentsPolicy = policy;
+ }
+
+ @Override
+ ParceledListSlice<RecentTaskInfo> getRecentTasks(int maxNum, int flags,
+ boolean getTasksAllowed,
+ boolean getDetailedTasks, int userId, int callingUid) {
+ mLastAllowed = getTasksAllowed;
+ return super.getRecentTasks(maxNum, flags, getTasksAllowed, getDetailedTasks, userId,
+ callingUid);
+ }
+ }
+
+ private static class TestRunningTasks extends RunningTasks {
+ boolean mLastAllowed;
+
+ @Override
+ void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType,
+ int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
+ int callingUid, boolean allowed) {
+ mLastAllowed = allowed;
+ super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays,
+ callingUid, allowed);
+ }
+ }
}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
new file mode 100644
index 0000000..fc75628
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RunningTasksTest extends ActivityTestsBase {
+
+ private Context mContext = InstrumentationRegistry.getContext();
+ private ActivityManagerService mService;
+
+ private RunningTasks mRunningTasks;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mService = createActivityManagerService();
+ mRunningTasks = new RunningTasks();
+ }
+
+ @Test
+ public void testCollectTasksByLastActiveTime() throws Exception {
+ // Create a number of stacks with tasks (of incrementing active time)
+ final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
+ final SparseArray<ActivityDisplay> displays = new SparseArray<>();
+ final ActivityDisplay display = new ActivityDisplay(supervisor, DEFAULT_DISPLAY);
+ displays.put(DEFAULT_DISPLAY, display);
+
+ final int numStacks = 2;
+ for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) {
+ final ActivityStack stack = new TestActivityStack(display, stackIndex, supervisor,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true);
+ display.addChild(stack, POSITION_BOTTOM);
+ }
+
+ final int numTasks = 10;
+ int activeTime = 0;
+ for (int i = 0; i < numTasks; i++) {
+ createTask(display.getChildAt(i % numStacks), ".Task" + i, i, activeTime++);
+ }
+
+ // Ensure that the latest tasks were returned in order of decreasing last active time,
+ // collected from all tasks across all the stacks
+ final int numFetchTasks = 5;
+ ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
+ mRunningTasks.getTasks(5, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
+ displays, -1 /* callingUid */, true /* allowed */);
+ assertTrue(tasks.size() == numFetchTasks);
+ for (int i = 0; i < numFetchTasks; i++) {
+ assertTrue(tasks.get(i).id == (numTasks - i - 1));
+ }
+
+ // Ensure that requesting more than the total number of tasks only returns the subset
+ // and does not crash
+ tasks.clear();
+ mRunningTasks.getTasks(100, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
+ displays, -1 /* callingUid */, true /* allowed */);
+ assertTrue(tasks.size() == numTasks);
+ for (int i = 0; i < numTasks; i++) {
+ assertTrue(tasks.get(i).id == (numTasks - i - 1));
+ }
+ }
+
+ /**
+ * Create a task with a single activity in it, with the given last active time.
+ */
+ private TaskRecord createTask(ActivityStack stack, String className, int taskId,
+ int lastActiveTime) {
+ final TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
+ .setComponent(new ComponentName(mContext.getPackageName(), className))
+ .setTaskId(taskId)
+ .setStack(stack)
+ .build();
+ task.lastActiveTime = lastActiveTime;
+ final ActivityRecord activity = new ActivityBuilder(mService)
+ .setTask(task)
+ .setComponent(new ComponentName(mContext.getPackageName(), ".TaskActivity"))
+ .build();
+ return task;
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index d6d0209..6fdb0298 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -60,7 +60,6 @@
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -104,7 +103,7 @@
public void setUp() throws Exception {
super.setUp();
mInjector = Mockito.spy(new TestInjector(getContext()));
- doNothing().when(mInjector).clearLockTaskMode(anyString());
+ doNothing().when(mInjector).clearAllLockedTasks(anyString());
doNothing().when(mInjector).startHomeActivity(anyInt(), anyString());
doReturn(false).when(mInjector).stackSupervisorSwitchUser(anyInt(), any());
doNothing().when(mInjector).stackSupervisorResumeFocusedStackTopActivity();
@@ -126,7 +125,7 @@
Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(true);
- Mockito.verify(mInjector).clearLockTaskMode(anyString());
+ Mockito.verify(mInjector).clearAllLockedTasks(anyString());
startForegroundUserAssertions();
}
@@ -136,7 +135,7 @@
Mockito.verify(
mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
- Mockito.verify(mInjector, never()).clearLockTaskMode(anyString());
+ Mockito.verify(mInjector, never()).clearAllLockedTasks(anyString());
startBackgroundUserAssertions();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index e1e9cf5..9d23fe9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3340,13 +3340,15 @@
MoreAsserts.assertEmpty(targetUsers);
}
- public void testLockTaskPackagesAllowedForAffiliatedUsers() throws Exception {
+ public void testLockTaskPolicyAllowedForAffiliatedUsers() throws Exception {
// Setup a device owner.
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
- // Lock task packages are updated when loading user data.
- verify(getServices().iactivityManager)
- .updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(new String[0]));
+ // Lock task policy is updated when loading user data.
+ verify(getServices().iactivityManager).updateLockTaskPackages(
+ UserHandle.USER_SYSTEM, new String[0]);
+ verify(getServices().iactivityManager).updateLockTaskFeatures(
+ UserHandle.USER_SYSTEM, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
// Set up a managed profile managed by different package (package name shouldn't matter)
final int MANAGED_PROFILE_USER_ID = 15;
@@ -3354,8 +3356,10 @@
final ComponentName adminDifferentPackage =
new ComponentName("another.package", "whatever.class");
addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
- verify(getServices().iactivityManager)
- .updateLockTaskPackages(eq(MANAGED_PROFILE_USER_ID), eq(new String[0]));
+ verify(getServices().iactivityManager).updateLockTaskPackages(
+ MANAGED_PROFILE_USER_ID, new String[0]);
+ verify(getServices().iactivityManager).updateLockTaskFeatures(
+ MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
// The DO can still set lock task packages
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
@@ -3364,8 +3368,14 @@
MoreAsserts.assertEquals(doPackages, dpm.getLockTaskPackages(admin1));
assertTrue(dpm.isLockTaskPermitted("doPackage1"));
assertFalse(dpm.isLockTaskPermitted("anotherPackage"));
- verify(getServices().iactivityManager)
- .updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(doPackages));
+ verify(getServices().iactivityManager).updateLockTaskPackages(
+ UserHandle.USER_SYSTEM, doPackages);
+ // And the DO can still set lock task features
+ final int doFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+ | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ dpm.setLockTaskFeatures(admin1, doFlags);
+ verify(getServices().iactivityManager).updateLockTaskFeatures(
+ UserHandle.USER_SYSTEM, doFlags);
// Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
@@ -3375,6 +3385,11 @@
assertExpectException(SecurityException.class, /* messageRegex =*/ null,
() -> dpm.getLockTaskPackages(adminDifferentPackage));
assertFalse(dpm.isLockTaskPermitted("doPackage1"));
+ // And it shouldn't be able to setLockTaskFeatures.
+ final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+ | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.setLockTaskFeatures(adminDifferentPackage, poFlags));
// Setting same affiliation ids
final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id");
@@ -3389,15 +3404,21 @@
MoreAsserts.assertEquals(poPackages, dpm.getLockTaskPackages(adminDifferentPackage));
assertTrue(dpm.isLockTaskPermitted("poPackage1"));
assertFalse(dpm.isLockTaskPermitted("doPackage2"));
- verify(getServices().iactivityManager)
- .updateLockTaskPackages(eq(MANAGED_PROFILE_USER_ID), eq(poPackages));
+ verify(getServices().iactivityManager).updateLockTaskPackages(
+ MANAGED_PROFILE_USER_ID, poPackages);
+ // And it can set lock task features.
+ dpm.setLockTaskFeatures(adminDifferentPackage, poFlags);
+ verify(getServices().iactivityManager).updateLockTaskFeatures(
+ MANAGED_PROFILE_USER_ID, poFlags);
// Unaffiliate the profile, lock task mode no longer available on the profile.
dpm.setAffiliationIds(adminDifferentPackage, Collections.emptySet());
assertFalse(dpm.isLockTaskPermitted("poPackage1"));
// Lock task packages cleared when loading user data and when the user becomes unaffiliated.
- verify(getServices().iactivityManager, times(2))
- .updateLockTaskPackages(eq(MANAGED_PROFILE_USER_ID), eq(new String[0]));
+ verify(getServices().iactivityManager, times(2)).updateLockTaskPackages(
+ MANAGED_PROFILE_USER_ID, new String[0]);
+ verify(getServices().iactivityManager, times(2)).updateLockTaskFeatures(
+ MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
assertTrue(dpm.isLockTaskPermitted("doPackage1"));
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
index 7a676e25..bb35beb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
@@ -154,9 +154,6 @@
File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
systemDeDir.mkdirs();
writeFile(new File(systemDeDir, "file"), "-----" );
- File miscDeDir = mUserDataPreparer.getDataMiscDeDirectory(TEST_USER_ID);
- miscDeDir.mkdirs();
- writeFile(new File(miscDeDir, "file"), "-----" );
mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_DE);
@@ -168,8 +165,6 @@
assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(systemDir)));
assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
systemDeDir)));
- assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
- miscDeDir)));
}
@Test
@@ -177,9 +172,6 @@
File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
systemCeDir.mkdirs();
writeFile(new File(systemCeDir, "file"), "-----" );
- File miscCeDir = mUserDataPreparer.getDataMiscCeDirectory(TEST_USER_ID);
- miscCeDir.mkdirs();
- writeFile(new File(miscCeDir, "file"), "-----" );
mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_CE);
@@ -190,8 +182,6 @@
assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
systemCeDir)));
- assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
- miscCeDir)));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
index b64716c..c2072df 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -52,6 +52,7 @@
assertFalse(opt.isDexoptOnlySharedDex());
assertFalse(opt.isDowngrade());
assertFalse(opt.isForce());
+ assertFalse(opt.isDexoptIdleBackgroundJob());
}
@Test
@@ -63,7 +64,8 @@
DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
DexoptOptions.DEXOPT_ONLY_SHARED_DEX |
DexoptOptions.DEXOPT_DOWNGRADE |
- DexoptOptions.DEXOPT_AS_SHARED_LIBRARY;
+ DexoptOptions.DEXOPT_AS_SHARED_LIBRARY |
+ DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, flags);
assertEquals(mPackageName, opt.getPackageName());
@@ -76,6 +78,7 @@
assertTrue(opt.isDowngrade());
assertTrue(opt.isForce());
assertTrue(opt.isDexoptAsSharedLibrary());
+ assertTrue(opt.isDexoptIdleBackgroundJob());
}
@Test
@@ -137,4 +140,4 @@
assertTrue(gotException);
}
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
index 42ddedf..67ffe58 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
@@ -16,6 +16,11 @@
package com.android.server.usage;
+import static android.app.usage.AppStandby.REASON_TIMEOUT;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE;
+
+import android.app.usage.AppStandby;
import android.os.FileUtils;
import android.test.AndroidTestCase;
@@ -28,6 +33,8 @@
final static String PACKAGE_1 = "com.android.testpackage1";
final static String PACKAGE_2 = "com.android.testpackage2";
+ final static int USER_ID = 0;
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -42,7 +49,6 @@
}
public void testFilesCreation() {
- final int userId = 0;
AppIdleHistory aih = new AppIdleHistory(mStorageDir, 0);
aih.updateDisplay(true, /* elapsedRealtime= */ 1000);
@@ -50,9 +56,9 @@
// Screen On time file should be written right away
assertTrue(aih.getScreenOnTimeFile().exists());
- aih.writeAppIdleTimes(userId);
+ aih.writeAppIdleTimes(USER_ID);
// stats file should be written now
- assertTrue(new File(new File(mStorageDir, "users/" + userId),
+ assertTrue(new File(new File(mStorageDir, "users/" + USER_ID),
AppIdleHistory.APP_IDLE_FILENAME).exists());
}
@@ -77,24 +83,33 @@
assertEquals(aih2.getScreenOnTime(13000), 4000);
}
- public void testPackageEvents() {
+ public void testBuckets() {
AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
- aih.setThresholds(4000, 1000);
- aih.updateDisplay(true, 1000);
- // App is not-idle by default
- assertFalse(aih.isIdle(PACKAGE_1, 0, 1500));
- // Still not idle
- assertFalse(aih.isIdle(PACKAGE_1, 0, 3000));
- // Idle now
- assertTrue(aih.isIdle(PACKAGE_1, 0, 8000));
- // Not idle
- assertFalse(aih.isIdle(PACKAGE_2, 0, 9000));
- // Screen off
- aih.updateDisplay(false, 9100);
- // Still idle after 10 seconds because screen hasn't been on long enough
- assertFalse(aih.isIdle(PACKAGE_2, 0, 20000));
- aih.updateDisplay(true, 21000);
- assertTrue(aih.isIdle(PACKAGE_2, 0, 23000));
+ aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 1000, STANDBY_BUCKET_ACTIVE,
+ AppStandby.REASON_USAGE);
+ // ACTIVE means not idle
+ assertFalse(aih.isIdle(PACKAGE_1, USER_ID, 2000));
+
+ aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE,
+ AppStandby.REASON_USAGE);
+ aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE,
+ REASON_TIMEOUT);
+
+ assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 3000), STANDBY_BUCKET_RARE);
+ assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 3000), STANDBY_BUCKET_ACTIVE);
+ assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_TIMEOUT);
+
+ // RARE is considered idle
+ assertTrue(aih.isIdle(PACKAGE_1, USER_ID, 3000));
+ assertFalse(aih.isIdle(PACKAGE_2, USER_ID, 3000));
+
+ // Check persistence
+ aih.writeAppIdleDurations();
+ aih.writeAppIdleTimes(USER_ID);
+ aih = new AppIdleHistory(mStorageDir, 4000);
+ assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE);
+ assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE);
+ assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_TIMEOUT);
}
}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
new file mode 100644
index 0000000..9846d6f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usage;
+
+import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_FREQUENT;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_WORKING_SET;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.app.usage.AppStandby;
+import android.app.usage.UsageEvents;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Display;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test for AppStandbyController.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AppStandbyControllerTests {
+
+ private static final String PACKAGE_1 = "com.example.foo";
+ private static final int UID_1 = 10000;
+ private static final int USER_ID = 0;
+
+ private static final long MINUTE_MS = 60 * 1000;
+ private static final long HOUR_MS = 60 * MINUTE_MS;
+ private static final long DAY_MS = 24 * HOUR_MS;
+
+ private MyInjector mInjector;
+
+ static class MyContextWrapper extends ContextWrapper {
+ PackageManager mockPm = mock(PackageManager.class);
+
+ public MyContextWrapper(Context base) {
+ super(base);
+ }
+
+ public PackageManager getPackageManager() {
+ return mockPm;
+ }
+ }
+
+ static class MyInjector extends AppStandbyController.Injector {
+ long mElapsedRealtime;
+ boolean mIsCharging;
+ List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>();
+ boolean mDisplayOn;
+ DisplayManager.DisplayListener mDisplayListener;
+ String mBoundWidgetPackage;
+
+ MyInjector(Context context, Looper looper) {
+ super(context, looper);
+ }
+
+ @Override
+ void onBootPhase(int phase) {
+ }
+
+ @Override
+ int getBootPhase() {
+ return SystemService.PHASE_BOOT_COMPLETED;
+ }
+
+ @Override
+ long elapsedRealtime() {
+ return mElapsedRealtime;
+ }
+
+ @Override
+ long currentTimeMillis() {
+ return mElapsedRealtime;
+ }
+
+ @Override
+ boolean isAppIdleEnabled() {
+ return true;
+ }
+
+ @Override
+ boolean isCharging() {
+ return mIsCharging;
+ }
+
+ @Override
+ boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
+ return mPowerSaveWhitelistExceptIdle.contains(packageName);
+ }
+
+ @Override
+ File getDataSystemDirectory() {
+ return new File(getContext().getFilesDir(), Long.toString(Math.randomLongInternal()));
+ }
+
+ @Override
+ void noteEvent(int event, String packageName, int uid) throws RemoteException {
+ }
+
+ @Override
+ boolean isPackageEphemeral(int userId, String packageName) {
+ // TODO: update when testing ephemeral apps scenario
+ return false;
+ }
+
+ @Override
+ int[] getRunningUserIds() {
+ return new int[] {USER_ID};
+ }
+
+ @Override
+ boolean isDefaultDisplayOn() {
+ return mDisplayOn;
+ }
+
+ @Override
+ void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) {
+ mDisplayListener = listener;
+ }
+
+ @Override
+ String getActiveNetworkScorer() {
+ return null;
+ }
+
+ @Override
+ public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName,
+ int userId) {
+ return packageName != null && packageName.equals(mBoundWidgetPackage);
+ }
+
+ // Internal methods
+
+ void setDisplayOn(boolean on) {
+ mDisplayOn = on;
+ if (mDisplayListener != null) {
+ mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
+ }
+ }
+ }
+
+ private void setupPm(PackageManager mockPm) throws PackageManager.NameNotFoundException {
+ List<PackageInfo> packages = new ArrayList<>();
+ PackageInfo pi = new PackageInfo();
+ pi.applicationInfo = new ApplicationInfo();
+ pi.applicationInfo.uid = UID_1;
+ pi.packageName = PACKAGE_1;
+ packages.add(pi);
+
+ doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt());
+ try {
+ doReturn(UID_1).when(mockPm).getPackageUidAsUser(anyString(), anyInt(), anyInt());
+ doReturn(pi.applicationInfo).when(mockPm).getApplicationInfo(anyString(), anyInt());
+ } catch (PackageManager.NameNotFoundException nnfe) {}
+ }
+
+ private void setChargingState(AppStandbyController controller, boolean charging) {
+ mInjector.mIsCharging = charging;
+ if (controller != null) {
+ controller.setChargingState(charging);
+ }
+ }
+
+ private AppStandbyController setupController() throws Exception {
+ mInjector.mElapsedRealtime = 0;
+ AppStandbyController controller = new AppStandbyController(mInjector);
+ controller.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+ controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ mInjector.setDisplayOn(false);
+ mInjector.setDisplayOn(true);
+ setChargingState(controller, false);
+ setupPm(mInjector.getContext().getPackageManager());
+ controller.checkIdleStates(USER_ID);
+
+ return controller;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext());
+ mInjector = new MyInjector(myContext, Looper.getMainLooper());
+ }
+
+ @Test
+ public void testCharging() throws Exception {
+ AppStandbyController controller = setupController();
+
+ setChargingState(controller, true);
+ mInjector.mElapsedRealtime = 8 * DAY_MS;
+ assertFalse(controller.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID,
+ mInjector.mElapsedRealtime, false));
+
+ setChargingState(controller, false);
+ mInjector.mElapsedRealtime = 16 * DAY_MS;
+ controller.checkIdleStates(USER_ID);
+ assertTrue(controller.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID,
+ mInjector.mElapsedRealtime, false));
+ setChargingState(controller, true);
+ assertFalse(controller.isAppIdleFilteredOrParoled(PACKAGE_1,USER_ID,
+ mInjector.mElapsedRealtime, false));
+ }
+
+ private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) {
+ mInjector.mElapsedRealtime = elapsedTime;
+ controller.checkIdleStates(USER_ID);
+ assertEquals(bucket,
+ controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime,
+ false));
+ }
+
+ @Test
+ public void testBuckets() throws Exception {
+ AppStandbyController controller = setupController();
+
+ // ACTIVE bucket
+ assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE);
+
+ // WORKING_SET bucket
+ assertTimeout(controller, 25 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+
+ // WORKING_SET bucket
+ assertTimeout(controller, 47 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+
+ // FREQUENT bucket
+ assertTimeout(controller, 4 * DAY_MS, STANDBY_BUCKET_FREQUENT);
+
+ // RARE bucket
+ assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_RARE);
+
+ // Back to ACTIVE on event
+ UsageEvents.Event ev = new UsageEvents.Event();
+ ev.mPackage = PACKAGE_1;
+ ev.mEventType = UsageEvents.Event.USER_INTERACTION;
+ controller.reportEvent(ev, mInjector.mElapsedRealtime, USER_ID);
+
+ assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_ACTIVE);
+
+ // RARE bucket
+ assertTimeout(controller, 18 * DAY_MS, STANDBY_BUCKET_RARE);
+ }
+
+ @Test
+ public void testScreenTimeAndBuckets() throws Exception {
+ AppStandbyController controller = setupController();
+ mInjector.setDisplayOn(false);
+
+ // ACTIVE bucket
+ assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE);
+
+ // WORKING_SET bucket
+ assertTimeout(controller, 25 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+
+ // RARE bucket, should fail because the screen wasn't ON.
+ mInjector.mElapsedRealtime = 9 * DAY_MS;
+ controller.checkIdleStates(USER_ID);
+ assertNotEquals(STANDBY_BUCKET_RARE,
+ controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime,
+ false));
+
+ mInjector.setDisplayOn(true);
+ assertTimeout(controller, 18 * DAY_MS, STANDBY_BUCKET_RARE);
+ }
+
+ @Test
+ public void testForcedIdle() throws Exception {
+ AppStandbyController controller = setupController();
+ setChargingState(controller, false);
+
+ controller.forceIdleState(PACKAGE_1, USER_ID, true);
+ assertEquals(STANDBY_BUCKET_RARE, controller.getAppStandbyBucket(PACKAGE_1, USER_ID, 0,
+ true));
+ assertTrue(controller.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+
+ controller.forceIdleState(PACKAGE_1, USER_ID, false);
+ assertEquals(STANDBY_BUCKET_ACTIVE, controller.getAppStandbyBucket(PACKAGE_1, USER_ID, 0,
+ true));
+ assertFalse(controller.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java
index 8a312f6..da45d81 100644
--- a/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java
@@ -19,6 +19,7 @@
import static com.android.server.utils.PriorityDump.dump;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.verify;
@@ -59,13 +60,13 @@
@Test
public void testNullArgs() {
dump(mDumper, mFd, mPw, null);
- verify(mDumper).dump(same(mFd), same(mPw), eq(null));
+ verify(mDumper).dump(same(mFd), same(mPw), eq(null), /* asProto= */ eq(false));
}
@Test
public void testNoArgs() {
dump(mDumper, mFd, mPw, EMPTY_ARGS);
- verify(mDumper).dump(same(mFd), same(mPw), same(EMPTY_ARGS));
+ verify(mDumper).dump(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(false));
}
@Test
@@ -74,7 +75,7 @@
"--dumb_priority"
};
dump(mDumper, mFd, mPw, args);
- verify(mDumper).dump(same(mFd), same(mPw), same(args));
+ verify(mDumper).dump(same(mFd), same(mPw), eq(args), /* asProto= */ eq(false));
}
@Test
@@ -83,7 +84,7 @@
"--dump-priority"
};
dump(mDumper, mFd, mPw, args);
- verify(mDumper).dump(same(mFd), same(mPw), same(args));
+ verify(mDumper).dump(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(false));
}
@Test
@@ -92,7 +93,7 @@
"--dump-priority", "SUPER_HIGH"
};
dump(mDumper, mFd, mPw, args);
- verify(mDumper).dump(same(mFd), same(mPw), same(args));
+ verify(mDumper).dump(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(false));
}
@Test
@@ -101,7 +102,9 @@
"--dump-priority", "SUPER_HIGH", "--high", "--five"
};
dump(mDumper, mFd, mPw, args);
- verify(mDumper).dump(same(mFd), same(mPw), same(args));
+ verify(mDumper).dump(same(mFd), same(mPw), eq(new String[] {
+ "--high", "--five"
+ }), /* asProto= */ eq(false));
}
@Test
@@ -117,13 +120,13 @@
assertSame(mFd, fakeDumper.criticalFd);
assertSame(mPw, fakeDumper.criticalPw);
- assertSame(args, fakeDumper.criticalArgs);
+ assertArrayEquals(args, fakeDumper.criticalArgs);
assertSame(mFd, fakeDumper.highFd);
assertSame(mPw, fakeDumper.highPw);
- assertSame(args, fakeDumper.highArgs);
+ assertArrayEquals(args, fakeDumper.highArgs);
assertSame(mFd, fakeDumper.normalFd);
assertSame(mPw, fakeDumper.normalPw);
- assertSame(args, fakeDumper.normalArgs);
+ assertArrayEquals(args, fakeDumper.normalArgs);
}
@Test
@@ -131,7 +134,8 @@
dump(mDumper, mFd, mPw, new String[] {
"--dump-priority", "CRITICAL"
});
- verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(EMPTY_ARGS));
+ verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(EMPTY_ARGS),
+ /* asProto= */ eq(false));
}
@Test
@@ -141,7 +145,27 @@
});
verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(new String[] {
"--high", "--five"
- }));
+ }), /* asProto= */ eq(false));
+ }
+
+ @Test
+ public void testCriticalExtraArgsInMiddle() {
+ dump(mDumper, mFd, mPw, new String[] {
+ "--high", "--dump-priority", "CRITICAL", "--five"
+ });
+ verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(new String[] {
+ "--high", "--five"
+ }), /* asProto= */ eq(false));
+ }
+
+ @Test
+ public void testCriticalExtraArgsAtEnd() {
+ dump(mDumper, mFd, mPw, new String[] {
+ "--high", "--five", "--dump-priority", "CRITICAL"
+ });
+ verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(new String[] {
+ "--high", "--five"
+ }), /* asProto= */ eq(false));
}
@Test
@@ -149,7 +173,7 @@
dump(mDumper, mFd, mPw, new String[] {
"--dump-priority", "HIGH"
});
- verify(mDumper).dumpHigh(same(mFd), same(mPw), eq(EMPTY_ARGS));
+ verify(mDumper).dumpHigh(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(false));
}
@Test
@@ -159,7 +183,7 @@
});
verify(mDumper).dumpHigh(same(mFd), same(mPw), eq(new String[] {
"--high", "--five"
- }));
+ }), /* asProto= */ eq(false));
}
@Test
@@ -167,17 +191,58 @@
dump(mDumper, mFd, mPw, new String[] {
"--dump-priority", "NORMAL"
});
- verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(EMPTY_ARGS));
+ verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(false));
}
@Test
public void testNormalExtraArgs() {
- dump(mDumper, mFd, mPw, new String[] {
+ dump(mDumper, mFd, mPw, new String[]{
"--dump-priority", "NORMAL", "--high", "--five"
});
- verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(new String[] {
+ verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(new String[]{
"--high", "--five"
- }));
+ }), /* asProto= */ eq(false));
+ }
+
+ @Test
+ public void testProtoArgs() {
+ dump(mDumper, mFd, mPw, new String[]{"--proto"});
+ verify(mDumper).dump(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(true));
+ }
+
+ @Test
+ public void testProtoArgsWithPriorityArgs() {
+ dump(mDumper, mFd, mPw, new String[]{"--proto", "--dump-priority", "NORMAL", "--five"});
+ verify(mDumper).dumpNormal(same(mFd), same(mPw),
+ eq(new String[]{"--five"}), /* asProto= */ eq(true));
+ }
+
+ @Test
+ public void testProtoArgsWithPriorityArgsReverseOrder() {
+ dump(mDumper, mFd, mPw, new String[]{"--dump-priority", "NORMAL", "--proto", "--five"});
+ verify(mDumper).dumpNormal(same(mFd), same(mPw),
+ eq(new String[]{"--five"}), /* asProto= */ eq(true));
+ }
+
+ @Test
+ public void testProtoArgsInMiddle() {
+ dump(mDumper, mFd, mPw, new String[]{"--unknown", "--proto", "--five"});
+ verify(mDumper).dump(same(mFd), same(mPw),
+ eq(new String[]{"--unknown", "--five"}), /* asProto= */ eq(true));
+ }
+
+ @Test
+ public void testProtoArgsAtEnd() {
+ dump(mDumper, mFd, mPw, new String[]{"args", "-left", "--behind", "--proto"});
+ verify(mDumper).dump(same(mFd), same(mPw),
+ eq(new String[]{"args", "-left", "--behind"}), /* asProto= */ eq(true));
+ }
+
+ @Test
+ public void testProtoArgsWithInvalidPriorityType() {
+ dump(mDumper, mFd, mPw, new String[]{"--dump-priority", "HIGH?", "--proto"});
+ verify(mDumper).dump(same(mFd), same(mPw),
+ eq(EMPTY_ARGS), /* asProto= */ eq(true));
}
private final class FakeDumper implements PriorityDumper {
@@ -187,21 +252,22 @@
PrintWriter criticalPw, highPw, normalPw;
@Override
- public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean asProto) {
criticalFd = fd;
criticalPw = pw;
criticalArgs = args;
}
@Override
- public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
highFd = fd;
highPw = pw;
highArgs = args;
}
@Override
- public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
normalFd = fd;
normalPw = pw;
normalArgs = args;
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 9ad7add..ea4f4b9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -193,7 +193,7 @@
token.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
token.setFillsParent(false);
- // Can specify orientation if app doesn't fill parent. Allowed for SDK <= 25.
+ // Can specify orientation if app doesn't fill parent.
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation());
token.setFillsParent(true);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index b8e8946..0980f7e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -63,7 +63,7 @@
class WindowTestsBase {
static WindowManagerService sWm = null;
private static final IWindow sIWindow = new TestIWindow();
- private static final Session sMockSession = mock(Session.class);
+ private static Session sMockSession;
// The default display is removed in {@link #setUp} and then we iterate over all displays to
// make sure we don't collide with any existing display. If we run into no other display, the
// added display should be treated as default. This cannot be the default display
@@ -93,6 +93,7 @@
// Allows to mock package local classes and methods
System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
+ sMockSession = mock(Session.class);
}
final Context context = InstrumentationRegistry.getTargetContext();
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java
new file mode 100644
index 0000000..ad9aea7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+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.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.wm.proto.WindowManagerTraceProto;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Test class for {@link WindowTracing}.
+ *
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.wm.WindowTracingTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowTracingTest extends WindowTestsBase {
+
+ private static final byte[] MAGIC_HEADER = new byte[] {
+ 0x9, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45,
+ };
+
+ private Context mTestContext;
+ private WindowTracing mWindowTracing;
+ private WindowManagerService mWmMock;
+ private File mFile;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mWmMock = mock(WindowManagerService.class);
+
+ mTestContext = InstrumentationRegistry.getContext();
+
+ mFile = mTestContext.getFileStreamPath("tracing_test.dat");
+ mFile.delete();
+
+ mWindowTracing = new WindowTracing(mFile);
+ }
+
+ @Test
+ public void isEnabled_returnsFalseByDefault() throws Exception {
+ assertFalse(mWindowTracing.isEnabled());
+ }
+
+ @Test
+ public void isEnabled_returnsTrueAfterStart() throws Exception {
+ mWindowTracing.startTrace(mock(PrintWriter.class));
+ assertTrue(mWindowTracing.isEnabled());
+ }
+
+ @Test
+ public void isEnabled_returnsFalseAfterStop() throws Exception {
+ mWindowTracing.startTrace(mock(PrintWriter.class));
+ mWindowTracing.stopTrace(mock(PrintWriter.class));
+ assertFalse(mWindowTracing.isEnabled());
+ }
+
+ @Test
+ public void trace_discared_whenNotTracing() throws Exception {
+ mWindowTracing.traceStateLocked("where", mWmMock);
+ verifyZeroInteractions(mWmMock);
+ }
+
+ @Test
+ public void trace_dumpsWindowManagerState_whenTracing() throws Exception {
+ mWindowTracing.startTrace(mock(PrintWriter.class));
+ mWindowTracing.traceStateLocked("where", mWmMock);
+
+ verify(mWmMock).writeToProtoLocked(any(), eq(true));
+ }
+
+ @Test
+ public void traceFile_startsWithMagicHeader() throws Exception {
+ mWindowTracing.startTrace(mock(PrintWriter.class));
+ mWindowTracing.stopTrace(mock(PrintWriter.class));
+
+ byte[] header = new byte[MAGIC_HEADER.length];
+ try (InputStream is = new FileInputStream(mFile)) {
+ assertEquals(MAGIC_HEADER.length, is.read(header));
+ assertArrayEquals(MAGIC_HEADER, header);
+ }
+ }
+
+ @Test
+ @Ignore("Figure out why this test is crashing when setting up mWmMock.")
+ public void tracing_endsUpInFile() throws Exception {
+ mWindowTracing.startTrace(mock(PrintWriter.class));
+
+ doAnswer((inv) -> {
+ inv.<ProtoOutputStream>getArgument(0).write(
+ WindowManagerTraceProto.WHERE, "TEST_WM_PROTO");
+ return null;
+ }).when(mWmMock).writeToProtoLocked(any(), any());
+ mWindowTracing.traceStateLocked("TEST_WHERE", mWmMock);
+
+ mWindowTracing.stopTrace(mock(PrintWriter.class));
+
+ byte[] file = new byte[1000];
+ int fileLength;
+ try (InputStream is = new FileInputStream(mFile)) {
+ fileLength = is.read(file);
+ assertTrue(containsBytes(file, fileLength,
+ "TEST_WHERE".getBytes(StandardCharsets.UTF_8)));
+ assertTrue(containsBytes(file, fileLength,
+ "TEST_WM_PROTO".getBytes(StandardCharsets.UTF_8)));
+ }
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+
+ mFile.delete();
+ }
+
+ /** Return true if {@code needle} appears anywhere in {@code haystack[0..length]} */
+ boolean containsBytes(byte[] haystack, int haystackLenght, byte[] needle) {
+ Preconditions.checkArgument(haystackLenght > 0);
+ Preconditions.checkArgument(needle.length > 0);
+
+ outer: for (int i = 0; i <= haystackLenght - needle.length; i++) {
+ for (int j = 0; j < needle.length; j++) {
+ if (haystack[i+j] != needle[j]) {
+ continue outer;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Test
+ public void test_containsBytes() {
+ byte[] haystack = "hello_world".getBytes(StandardCharsets.UTF_8);
+ assertTrue(containsBytes(haystack, haystack.length,
+ "hello".getBytes(StandardCharsets.UTF_8)));
+ assertTrue(containsBytes(haystack, haystack.length,
+ "world".getBytes(StandardCharsets.UTF_8)));
+ assertFalse(containsBytes(haystack, 6,
+ "world".getBytes(StandardCharsets.UTF_8)));
+ assertFalse(containsBytes(haystack, haystack.length,
+ "world_".getBytes(StandardCharsets.UTF_8)));
+ assertFalse(containsBytes(haystack, haystack.length,
+ "absent".getBytes(StandardCharsets.UTF_8)));
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index f298559..e5d3915 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -16,6 +16,9 @@
package com.android.server.usage;
+import static android.app.usage.AppStandby.*;
+
+import android.app.usage.AppStandby;
import android.os.Environment;
import android.os.SystemClock;
import android.util.ArrayMap;
@@ -51,8 +54,10 @@
private static final String TAG = "AppIdleHistory";
+ private static final boolean DEBUG = AppStandbyController.DEBUG;
+
// History for all users and all packages
- private SparseArray<ArrayMap<String,PackageHistory>> mIdleHistory = new SparseArray<>();
+ private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
private long mLastPeriod = 0;
private static final long ONE_MINUTE = 60 * 1000;
private static final int HISTORY_SIZE = 100;
@@ -70,6 +75,13 @@
private static final String ATTR_SCREEN_IDLE = "screenIdleTime";
// Elapsed timebase time when app was last used
private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime";
+ private static final String ATTR_CURRENT_BUCKET = "appLimitBucket";
+ private static final String ATTR_BUCKETING_REASON = "bucketReason";
+
+ // State that was last informed to listeners, since boot
+ private static final int STATE_UNINFORMED = 0;
+ private static final int STATE_ACTIVE = 1;
+ private static final int STATE_IDLE = 2;
// device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
@@ -85,17 +97,15 @@
private boolean mScreenOn;
- private static class PackageHistory {
+ private static class AppUsageHistory {
final byte[] recent = new byte[HISTORY_SIZE];
long lastUsedElapsedTime;
long lastUsedScreenTime;
+ @StandbyBuckets int currentBucket;
+ String bucketingReason;
+ int lastInformedState;
}
- AppIdleHistory(long elapsedRealtime) {
- this(Environment.getDataSystemDirectory(), elapsedRealtime);
- }
-
- @VisibleForTesting
AppIdleHistory(File storageDir, long elapsedRealtime) {
mElapsedSnapshot = elapsedRealtime;
mScreenOnSnapshot = elapsedRealtime;
@@ -119,6 +129,9 @@
mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
mElapsedSnapshot = elapsedRealtime;
}
+ if (DEBUG) Slog.d(TAG, "mScreenOnSnapshot=" + mScreenOnSnapshot
+ + ", mScreenOnDuration=" + mScreenOnDuration
+ + ", mScreenOn=" + mScreenOn);
}
public long getScreenOnTime(long elapsedRealtime) {
@@ -174,29 +187,35 @@
}
public void reportUsage(String packageName, int userId, long elapsedRealtime) {
- ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
- PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
- elapsedRealtime);
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+ elapsedRealtime, true);
shiftHistoryToNow(userHistory, elapsedRealtime);
- packageHistory.lastUsedElapsedTime = mElapsedDuration
+ appUsageHistory.lastUsedElapsedTime = mElapsedDuration
+ (elapsedRealtime - mElapsedSnapshot);
- packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
- packageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
+ appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
+ appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
+ appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_ACTIVE;
+ appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
+ if (DEBUG) {
+ Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+ + ", reason=" + appUsageHistory.bucketingReason);
+ }
}
public void setIdle(String packageName, int userId, long elapsedRealtime) {
- ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
- PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
- elapsedRealtime);
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+ elapsedRealtime, true);
shiftHistoryToNow(userHistory, elapsedRealtime);
- packageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
+ appUsageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
}
- private void shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory,
+ private void shiftHistoryToNow(ArrayMap<String, AppUsageHistory> userHistory,
long elapsedRealtime) {
long thisPeriod = elapsedRealtime / PERIOD_DURATION;
// Has the period switched over? Slide all users' package histories
@@ -206,7 +225,7 @@
final int NUSERS = mIdleHistory.size();
for (int u = 0; u < NUSERS; u++) {
userHistory = mIdleHistory.valueAt(u);
- for (PackageHistory idleState : userHistory.values()) {
+ for (AppUsageHistory idleState : userHistory.values()) {
// Shift left
System.arraycopy(idleState.recent, diff, idleState.recent, 0,
HISTORY_SIZE - diff);
@@ -221,8 +240,8 @@
mLastPeriod = thisPeriod;
}
- private ArrayMap<String, PackageHistory> getUserHistory(int userId) {
- ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+ private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
+ ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
if (userHistory == null) {
userHistory = new ArrayMap<>();
mIdleHistory.put(userId, userHistory);
@@ -231,16 +250,18 @@
return userHistory;
}
- private PackageHistory getPackageHistory(ArrayMap<String, PackageHistory> userHistory,
- String packageName, long elapsedRealtime) {
- PackageHistory packageHistory = userHistory.get(packageName);
- if (packageHistory == null) {
- packageHistory = new PackageHistory();
- packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
- packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
- userHistory.put(packageName, packageHistory);
+ private AppUsageHistory getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory,
+ String packageName, long elapsedRealtime, boolean create) {
+ AppUsageHistory appUsageHistory = userHistory.get(packageName);
+ if (appUsageHistory == null && create) {
+ appUsageHistory = new AppUsageHistory();
+ appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
+ appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
+ appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_NEVER;
+ appUsageHistory.bucketingReason = REASON_DEFAULT;
+ userHistory.put(packageName, appUsageHistory);
}
- return packageHistory;
+ return appUsageHistory;
}
public void onUserRemoved(int userId) {
@@ -248,48 +269,124 @@
}
public boolean isIdle(String packageName, int userId, long elapsedRealtime) {
- ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
- PackageHistory packageHistory =
- getPackageHistory(userHistory, packageName, elapsedRealtime);
- if (packageHistory == null) {
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory =
+ getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+ if (appUsageHistory == null) {
return false; // Default to not idle
} else {
- return hasPassedThresholds(packageHistory, elapsedRealtime);
+ return appUsageHistory.currentBucket >= AppStandby.STANDBY_BUCKET_RARE;
+ // Whether or not it's passed will now be externally calculated and the
+ // bucket will be pushed to the history using setAppStandbyBucket()
+ //return hasPassedThresholds(appUsageHistory, elapsedRealtime);
}
}
+ public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
+ int bucket, String reason) {
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory =
+ getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+ appUsageHistory.currentBucket = bucket;
+ appUsageHistory.bucketingReason = reason;
+ if (DEBUG) {
+ Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+ + ", reason=" + appUsageHistory.bucketingReason);
+ }
+ }
+
+ public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory =
+ getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+ return appUsageHistory.currentBucket;
+ }
+
+ public String getAppStandbyReason(String packageName, int userId, long elapsedRealtime) {
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory =
+ getPackageHistory(userHistory, packageName, elapsedRealtime, false);
+ return appUsageHistory != null ? appUsageHistory.bucketingReason : null;
+ }
+
private long getElapsedTime(long elapsedRealtime) {
return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
}
public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
- ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
- PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
- elapsedRealtime);
- packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime)
- - mElapsedTimeThreshold;
- packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime)
- - (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */;
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+ elapsedRealtime, true);
+ if (idle) {
+ appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
+ appUsageHistory.bucketingReason = REASON_FORCED;
+ } else {
+ appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
+ // This is to pretend that the app was just used, don't freeze the state anymore.
+ appUsageHistory.bucketingReason = REASON_USAGE;
+ }
}
public void clearUsage(String packageName, int userId) {
- ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
userHistory.remove(packageName);
}
- private boolean hasPassedThresholds(PackageHistory packageHistory, long elapsedRealtime) {
- return (packageHistory.lastUsedScreenTime
- <= getScreenOnTime(elapsedRealtime) - mScreenOnTimeThreshold)
- && (packageHistory.lastUsedElapsedTime
- <= getElapsedTime(elapsedRealtime) - mElapsedTimeThreshold);
+ boolean shouldInformListeners(String packageName, int userId,
+ long elapsedRealtime, boolean isIdle) {
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+ elapsedRealtime, true);
+ int targetState = isIdle? STATE_IDLE : STATE_ACTIVE;
+ if (appUsageHistory.lastInformedState != (isIdle ? STATE_IDLE : STATE_ACTIVE)) {
+ appUsageHistory.lastInformedState = targetState;
+ return true;
+ }
+ return false;
}
- private File getUserFile(int userId) {
+ /**
+ * Returns the index in the arrays of screenTimeThresholds and elapsedTimeThresholds
+ * that corresponds to how long since the app was used.
+ * @param packageName
+ * @param userId
+ * @param elapsedRealtime current time
+ * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0
+ * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0
+ * @return The index whose values the app's used time exceeds (in both arrays)
+ */
+ int getThresholdIndex(String packageName, int userId, long elapsedRealtime,
+ long[] screenTimeThresholds, long[] elapsedTimeThresholds) {
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+ elapsedRealtime, false);
+ // If we don't have any state for the app, assume never used
+ if (appUsageHistory == null) return screenTimeThresholds.length - 1;
+
+ long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime;
+ long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime;
+
+ if (DEBUG) Slog.d(TAG, packageName
+ + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime
+ + " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime);
+ if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta
+ + ", elapsed=" + elapsedDelta);
+ for (int i = screenTimeThresholds.length - 1; i >= 0; i--) {
+ if (screenOnDelta >= screenTimeThresholds[i]
+ && elapsedDelta >= elapsedTimeThresholds[i]) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ @VisibleForTesting
+ File getUserFile(int userId) {
return new File(new File(new File(mStorageDir, "users"),
Integer.toString(userId)), APP_IDLE_FILENAME);
}
- private void readAppIdleTimes(int userId, ArrayMap<String, PackageHistory> userHistory) {
+ private void readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory) {
FileInputStream fis = null;
try {
AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
@@ -315,12 +412,22 @@
final String name = parser.getName();
if (name.equals(TAG_PACKAGE)) {
final String packageName = parser.getAttributeValue(null, ATTR_NAME);
- PackageHistory packageHistory = new PackageHistory();
- packageHistory.lastUsedElapsedTime =
+ AppUsageHistory appUsageHistory = new AppUsageHistory();
+ appUsageHistory.lastUsedElapsedTime =
Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
- packageHistory.lastUsedScreenTime =
+ appUsageHistory.lastUsedScreenTime =
Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
- userHistory.put(packageName, packageHistory);
+ String currentBucketString = parser.getAttributeValue(null,
+ ATTR_CURRENT_BUCKET);
+ appUsageHistory.currentBucket = currentBucketString == null
+ ? AppStandby.STANDBY_BUCKET_ACTIVE
+ : Integer.parseInt(currentBucketString);
+ appUsageHistory.bucketingReason =
+ parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
+ if (appUsageHistory.bucketingReason == null) {
+ appUsageHistory.bucketingReason = REASON_DEFAULT;
+ }
+ userHistory.put(packageName, appUsageHistory);
}
}
}
@@ -345,17 +452,20 @@
xml.startTag(null, TAG_PACKAGES);
- ArrayMap<String,PackageHistory> userHistory = getUserHistory(userId);
+ ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId);
final int N = userHistory.size();
for (int i = 0; i < N; i++) {
String packageName = userHistory.keyAt(i);
- PackageHistory history = userHistory.valueAt(i);
+ AppUsageHistory history = userHistory.valueAt(i);
xml.startTag(null, TAG_PACKAGE);
xml.attribute(null, ATTR_NAME, packageName);
xml.attribute(null, ATTR_ELAPSED_IDLE,
Long.toString(history.lastUsedElapsedTime));
xml.attribute(null, ATTR_SCREEN_IDLE,
Long.toString(history.lastUsedScreenTime));
+ xml.attribute(null, ATTR_CURRENT_BUCKET,
+ Integer.toString(history.currentBucket));
+ xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason);
xml.endTag(null, TAG_PACKAGE);
}
@@ -368,10 +478,10 @@
}
}
- public void dump(IndentingPrintWriter idpw, int userId) {
+ public void dump(IndentingPrintWriter idpw, int userId, String pkg) {
idpw.println("Package idle stats:");
idpw.increaseIndent();
- ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+ ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long totalElapsedTime = getElapsedTime(elapsedRealtime);
final long screenOnTime = getScreenOnTime(elapsedRealtime);
@@ -379,13 +489,18 @@
final int P = userHistory.size();
for (int p = 0; p < P; p++) {
final String packageName = userHistory.keyAt(p);
- final PackageHistory packageHistory = userHistory.valueAt(p);
+ final AppUsageHistory appUsageHistory = userHistory.valueAt(p);
+ if (pkg != null && !pkg.equals(packageName)) {
+ continue;
+ }
idpw.print("package=" + packageName);
idpw.print(" lastUsedElapsed=");
- TimeUtils.formatDuration(totalElapsedTime - packageHistory.lastUsedElapsedTime, idpw);
+ TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw);
idpw.print(" lastUsedScreenOn=");
- TimeUtils.formatDuration(screenOnTime - packageHistory.lastUsedScreenTime, idpw);
+ TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
+ idpw.print(" bucket=" + appUsageHistory.currentBucket
+ + " reason=" + appUsageHistory.bucketingReason);
idpw.println();
}
idpw.println();
@@ -399,7 +514,7 @@
}
public void dumpHistory(IndentingPrintWriter idpw, int userId) {
- ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+ ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
final long elapsedRealtime = SystemClock.elapsedRealtime();
if (userHistory == null) return;
final int P = userHistory.size();
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index b2446ba..17fde57 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -18,13 +18,14 @@
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
-import static com.android.server.usage.UsageStatsService.MSG_REPORT_EVENT;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
+import android.app.usage.AppStandby;
+import android.app.usage.AppStandby.StandbyBuckets;
import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -41,6 +42,7 @@
import android.net.NetworkScoreManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
+import android.os.Environment;
import android.os.Handler;
import android.os.IDeviceIdleController;
import android.os.Looper;
@@ -66,8 +68,10 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
+import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -76,10 +80,33 @@
public class AppStandbyController {
private static final String TAG = "AppStandbyController";
- private static final boolean DEBUG = false;
+ static final boolean DEBUG = false;
static final boolean COMPRESS_TIME = false;
private static final long ONE_MINUTE = 60 * 1000;
+ private static final long ONE_HOUR = ONE_MINUTE * 60;
+ private static final long ONE_DAY = ONE_HOUR * 24;
+
+ static final long[] SCREEN_TIME_THRESHOLDS = {
+ 0,
+ 0,
+ COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR,
+ COMPRESS_TIME ? 240 * 1000 : 8 * ONE_HOUR
+ };
+
+ static final long[] ELAPSED_TIME_THRESHOLDS = {
+ 0,
+ COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR,
+ COMPRESS_TIME ? 4 * ONE_MINUTE : 2 * ONE_DAY,
+ COMPRESS_TIME ? 16 * ONE_MINUTE : 8 * ONE_DAY
+ };
+
+ static final int[] THRESHOLD_BUCKETS = {
+ AppStandby.STANDBY_BUCKET_ACTIVE,
+ AppStandby.STANDBY_BUCKET_WORKING_SET,
+ AppStandby.STANDBY_BUCKET_FREQUENT,
+ AppStandby.STANDBY_BUCKET_RARE
+ };
// To name the lock for stack traces
static class Lock {}
@@ -92,7 +119,7 @@
private AppIdleHistory mAppIdleHistory;
@GuardedBy("mAppIdleLock")
- private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
+ private ArrayList<AppIdleStateChangeListener>
mPackageAccessListeners = new ArrayList<>();
/** Whether we've queried the list of carrier privileged apps. */
@@ -118,6 +145,9 @@
long mAppIdleWallclockThresholdMillis;
long mAppIdleParoleIntervalMillis;
long mAppIdleParoleDurationMillis;
+ long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
+ long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS;
+
boolean mAppIdleEnabled;
boolean mAppIdleTempParoled;
boolean mCharging;
@@ -129,20 +159,26 @@
private final Handler mHandler;
private final Context mContext;
- private DisplayManager mDisplayManager;
- private IDeviceIdleController mDeviceIdleController;
+ // TODO: Provide a mechanism to set an external bucketing service
+ private boolean mUseInternalBucketingHeuristics = true;
+
private AppWidgetManager mAppWidgetManager;
- private IBatteryStats mBatteryStats;
private PowerManager mPowerManager;
private PackageManager mPackageManager;
- private PackageManagerInternal mPackageManagerInternal;
+ private Injector mInjector;
+
AppStandbyController(Context context, Looper looper) {
- mContext = context;
- mHandler = new AppStandbyHandler(looper);
+ this(new Injector(context, looper));
+ }
+
+ AppStandbyController(Injector injector) {
+ mInjector = injector;
+ mContext = mInjector.getContext();
+ mHandler = new AppStandbyHandler(mInjector.getLooper());
mPackageManager = mContext.getPackageManager();
- mAppIdleEnabled = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enableAutoPowerModes);
+ mAppIdleEnabled = mInjector.isAppIdleEnabled();
+
if (mAppIdleEnabled) {
IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
@@ -150,7 +186,8 @@
mContext.registerReceiver(new DeviceStateReceiver(), deviceStates);
}
synchronized (mAppIdleLock) {
- mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
+ mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
+ mInjector.elapsedRealtime());
}
IntentFilter packageFilter = new IntentFilter();
@@ -164,6 +201,7 @@
}
public void onBootPhase(int phase) {
+ mInjector.onBootPhase(phase);
if (phase == PHASE_SYSTEM_SERVICES_READY) {
// Observe changes to the threshold
SettingsObserver settingsObserver = new SettingsObserver(mHandler);
@@ -171,18 +209,11 @@
settingsObserver.updateSettings();
mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
- mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- mBatteryStats = IBatteryStats.Stub.asInterface(
- ServiceManager.getService(BatteryStats.SERVICE_NAME));
- mDisplayManager = (DisplayManager) mContext.getSystemService(
- Context.DISPLAY_SERVICE);
mPowerManager = mContext.getSystemService(PowerManager.class);
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+ mInjector.registerDisplayListener(mDisplayListener, mHandler);
synchronized (mAppIdleLock) {
- mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime());
+ mAppIdleHistory.updateDisplay(isDisplayOn(), mInjector.elapsedRealtime());
}
if (mPendingOneTimeCheckIdleStates) {
@@ -191,7 +222,7 @@
mSystemServicesReady = true;
} else if (phase == PHASE_BOOT_COMPLETED) {
- setChargingState(mContext.getSystemService(BatteryManager.class).isCharging());
+ setChargingState(mInjector.isCharging());
}
}
@@ -229,7 +260,7 @@
/** Paroled here means temporary pardon from being inactive */
void setAppIdleParoled(boolean paroled) {
synchronized (mAppIdleLock) {
- final long now = System.currentTimeMillis();
+ final long now = mInjector.currentTimeMillis();
if (mAppIdleTempParoled != paroled) {
mAppIdleTempParoled = paroled;
if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
@@ -284,7 +315,7 @@
* scheduling a series of repeating checkIdleStates each time we fired off one.
*/
void postOneTimeCheckIdleStates() {
- if (mDeviceIdleController == null) {
+ if (mInjector.getBootPhase() < PHASE_SYSTEM_SERVICES_READY) {
// Not booted yet; wait for it!
mPendingOneTimeCheckIdleStates = true;
} else {
@@ -304,7 +335,7 @@
final int[] runningUserIds;
try {
- runningUserIds = ActivityManager.getService().getRunningUserIds();
+ runningUserIds = mInjector.getRunningUserIds();
if (checkUserId != UserHandle.USER_ALL
&& !ArrayUtils.contains(runningUserIds, checkUserId)) {
return false;
@@ -313,7 +344,7 @@
throw re.rethrowFromSystemServer();
}
- final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long elapsedRealtime = mInjector.elapsedRealtime();
for (int i = 0; i < runningUserIds.length; i++) {
final int userId = runningUserIds[i];
if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
@@ -329,30 +360,71 @@
for (int p = 0; p < packageCount; p++) {
final PackageInfo pi = packages.get(p);
final String packageName = pi.packageName;
- final boolean isIdle = isAppIdleFiltered(packageName,
+ final boolean isSpecial = isAppSpecial(packageName,
UserHandle.getAppId(pi.applicationInfo.uid),
- userId, elapsedRealtime);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
- userId, isIdle ? 1 : 0, packageName));
- if (isIdle) {
+ userId);
+ if (DEBUG) {
+ Slog.d(TAG, " Checking idle state for " + packageName);
+ }
+ if (isSpecial) {
+ maybeInformListeners(packageName, userId, elapsedRealtime, false);
+ } else if (mUseInternalBucketingHeuristics) {
synchronized (mAppIdleLock) {
- mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
+ int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId,
+ elapsedRealtime);
+ String bucketingReason = mAppIdleHistory.getAppStandbyReason(packageName,
+ userId, elapsedRealtime);
+ if (bucketingReason != null
+ && (bucketingReason.equals(AppStandby.REASON_FORCED)
+ || bucketingReason.startsWith(AppStandby.REASON_PREDICTED))) {
+ continue;
+ }
+ int newBucket = getBucketForLocked(packageName, userId,
+ elapsedRealtime);
+ if (DEBUG) {
+ Slog.d(TAG, " Old bucket=" + oldBucket
+ + ", newBucket=" + newBucket);
+ }
+ if (oldBucket != newBucket) {
+ mAppIdleHistory.setAppStandbyBucket(packageName, userId,
+ elapsedRealtime, newBucket, AppStandby.REASON_TIMEOUT);
+ maybeInformListeners(packageName, userId, elapsedRealtime,
+ newBucket >= AppStandby.STANDBY_BUCKET_RARE);
+ }
}
}
}
}
if (DEBUG) {
Slog.d(TAG, "checkIdleStates took "
- + (SystemClock.elapsedRealtime() - elapsedRealtime));
+ + (mInjector.elapsedRealtime() - elapsedRealtime));
}
return true;
}
+ private void maybeInformListeners(String packageName, int userId,
+ long elapsedRealtime, boolean isIdle) {
+ synchronized (mAppIdleLock) {
+ if (mAppIdleHistory.shouldInformListeners(packageName, userId,
+ elapsedRealtime, isIdle)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
+ userId, isIdle ? 1 : 0, packageName));
+ }
+ }
+ }
+
+ @StandbyBuckets int getBucketForLocked(String packageName, int userId,
+ long elapsedRealtime) {
+ int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId,
+ elapsedRealtime, mAppStandbyScreenThresholds, mAppStandbyElapsedThresholds);
+ return THRESHOLD_BUCKETS[bucketIndex];
+ }
+
/** Check if it's been a while since last parole and let idle apps do some work */
void checkParoleTimeout() {
boolean setParoled = false;
synchronized (mAppIdleLock) {
- final long now = System.currentTimeMillis();
+ final long now = mInjector.currentTimeMillis();
if (!mAppIdleTempParoled) {
final long timeSinceLastParole = now - mLastAppIdleParoledTime;
if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
@@ -374,10 +446,10 @@
final int uid = mPackageManager.getPackageUidAsUser(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
if (idle) {
- mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
+ mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
packageName, uid);
} else {
- mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
+ mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
packageName, uid);
}
} catch (PackageManager.NameNotFoundException | RemoteException e) {
@@ -389,7 +461,7 @@
if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
boolean paroled = false;
synchronized (mAppIdleLock) {
- final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime;
+ final long timeSinceLastParole = mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
if (!deviceIdle
&& timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
if (DEBUG) {
@@ -419,13 +491,11 @@
|| event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
if (previouslyIdle) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
- /* idle = */ 0, event.mPackage));
+ maybeInformListeners(event.mPackage, userId, elapsedRealtime, false);
notifyBatteryStats(event.mPackage, userId, false);
}
}
}
-
}
/**
@@ -439,7 +509,7 @@
void forceIdleState(String packageName, int userId, boolean idle) {
final int appId = getAppId(packageName);
if (appId < 0) return;
- final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long elapsedRealtime = mInjector.elapsedRealtime();
final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
userId, elapsedRealtime);
@@ -470,7 +540,7 @@
}
}
- void addListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+ void addListener(AppIdleStateChangeListener listener) {
synchronized (mAppIdleLock) {
if (!mPackageAccessListeners.contains(listener)) {
mPackageAccessListeners.add(listener);
@@ -478,7 +548,7 @@
}
}
- void removeListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+ void removeListener(AppIdleStateChangeListener listener) {
synchronized (mAppIdleLock) {
mPackageAccessListeners.remove(listener);
}
@@ -501,12 +571,66 @@
return false;
}
if (shouldObfuscateInstantApps &&
- mPackageManagerInternal.isPackageEphemeral(userId, packageName)) {
+ mInjector.isPackageEphemeral(userId, packageName)) {
return false;
}
return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
}
+ /** Returns true if this app should be whitelisted for some reason, to never go into standby */
+ boolean isAppSpecial(String packageName, int appId, int userId) {
+ if (packageName == null) return false;
+ // If not enabled at all, of course nobody is ever idle.
+ if (!mAppIdleEnabled) {
+ return true;
+ }
+ if (appId < Process.FIRST_APPLICATION_UID) {
+ // System uids never go idle.
+ return true;
+ }
+ if (packageName.equals("android")) {
+ // Nor does the framework (which should be redundant with the above, but for MR1 we will
+ // retain this for safety).
+ return true;
+ }
+ if (mSystemServicesReady) {
+ try {
+ // We allow all whitelisted apps, including those that don't want to be whitelisted
+ // for idle mode, because app idle (aka app standby) is really not as big an issue
+ // for controlling who participates vs. doze mode.
+ if (mInjector.isPowerSaveWhitelistExceptIdleApp(packageName)) {
+ return true;
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+
+ if (isActiveDeviceAdmin(packageName, userId)) {
+ return true;
+ }
+
+ if (isActiveNetworkScorer(packageName)) {
+ return true;
+ }
+
+ if (mAppWidgetManager != null
+ && mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) {
+ return true;
+ }
+
+ if (isDeviceProvisioningPackage(packageName)) {
+ return true;
+ }
+ }
+
+ // Check this last, as it can be the most expensive check
+ if (isCarrierApp(packageName)) {
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Checks if an app has been idle for a while and filters out apps that are excluded.
* It returns false if the current system state allows all apps to be considered active.
@@ -515,61 +639,11 @@
*/
boolean isAppIdleFiltered(String packageName, int appId, int userId,
long elapsedRealtime) {
- if (packageName == null) return false;
- // If not enabled at all, of course nobody is ever idle.
- if (!mAppIdleEnabled) {
+ if (isAppSpecial(packageName, appId, userId)) {
return false;
+ } else {
+ return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
}
- if (appId < Process.FIRST_APPLICATION_UID) {
- // System uids never go idle.
- return false;
- }
- if (packageName.equals("android")) {
- // Nor does the framework (which should be redundant with the above, but for MR1 we will
- // retain this for safety).
- return false;
- }
- if (mSystemServicesReady) {
- try {
- // We allow all whitelisted apps, including those that don't want to be whitelisted
- // for idle mode, because app idle (aka app standby) is really not as big an issue
- // for controlling who participates vs. doze mode.
- if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
- return false;
- }
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
-
- if (isActiveDeviceAdmin(packageName, userId)) {
- return false;
- }
-
- if (isActiveNetworkScorer(packageName)) {
- return false;
- }
-
- if (mAppWidgetManager != null
- && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
- return false;
- }
-
- if (isDeviceProvisioningPackage(packageName)) {
- return false;
- }
- }
-
- if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
- return false;
- }
-
- // Check this last, as it is the most expensive check
- // TODO: Optimize this by fetching the carrier privileged apps ahead of time
- if (isCarrierApp(packageName)) {
- return false;
- }
-
- return true;
}
int[] getIdleUidsForUser(int userId) {
@@ -577,7 +651,7 @@
return new int[0];
}
- final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long elapsedRealtime = mInjector.elapsedRealtime();
List<ApplicationInfo> apps;
try {
@@ -613,7 +687,7 @@
}
}
if (DEBUG) {
- Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
+ Slog.d(TAG, "getIdleUids took " + (mInjector.elapsedRealtime() - elapsedRealtime));
}
int numIdle = 0;
for (int i = uidStates.size() - 1; i >= 0; i--) {
@@ -643,6 +717,21 @@
.sendToTarget();
}
+ @StandbyBuckets int getAppStandbyBucket(String packageName, int userId,
+ long elapsedRealtime, boolean shouldObfuscateInstantApps) {
+ if (shouldObfuscateInstantApps &&
+ mInjector.isPackageEphemeral(userId, packageName)) {
+ return AppStandby.STANDBY_BUCKET_ACTIVE;
+ }
+
+ return mAppIdleHistory.getAppStandbyBucket(packageName, userId, elapsedRealtime);
+ }
+
+ void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+ String reason, long elapsedRealtime) {
+ mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason);
+ }
+
private boolean isActiveDeviceAdmin(String packageName, int userId) {
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
if (dpm == null) return false;
@@ -662,7 +751,7 @@
private boolean isCarrierApp(String packageName) {
synchronized (mAppIdleLock) {
if (!mHaveCarrierPrivilegedApps) {
- fetchCarrierPrivilegedAppsLA();
+ fetchCarrierPrivilegedAppsLocked();
}
if (mCarrierPrivilegedApps != null) {
return mCarrierPrivilegedApps.contains(packageName);
@@ -682,7 +771,7 @@
}
@GuardedBy("mAppIdleLock")
- private void fetchCarrierPrivilegedAppsLA() {
+ private void fetchCarrierPrivilegedAppsLocked() {
TelephonyManager telephonyManager =
mContext.getSystemService(TelephonyManager.class);
mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
@@ -693,20 +782,19 @@
}
private boolean isActiveNetworkScorer(String packageName) {
- NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService(
- Context.NETWORK_SCORE_SERVICE);
- return packageName != null && packageName.equals(nsm.getActiveScorerPackage());
+ String activeScorer = mInjector.getActiveNetworkScorer();
+ return packageName != null && packageName.equals(activeScorer);
}
void informListeners(String packageName, int userId, boolean isIdle) {
- for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+ for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
listener.onAppIdleStateChanged(packageName, userId, isIdle);
}
}
void informParoleStateChanged() {
final boolean paroled = isParoledOrCharging();
- for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+ for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
listener.onParoleStateChanged(paroled);
}
}
@@ -726,8 +814,7 @@
}
boolean isDisplayOn() {
- return mDisplayManager
- .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+ return mInjector.isDefaultDisplayOn();
}
void clearAppIdleForPackage(String packageName, int userId) {
@@ -755,7 +842,7 @@
void initializeDefaultsForSystemApps(int userId) {
Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
- final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long elapsedRealtime = mInjector.elapsedRealtime();
List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
PackageManager.MATCH_DISABLED_COMPONENTS,
userId);
@@ -786,9 +873,9 @@
}
}
- void dumpUser(IndentingPrintWriter idpw, int userId) {
+ void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) {
synchronized (mAppIdleLock) {
- mAppIdleHistory.dump(idpw, userId);
+ mAppIdleHistory.dump(idpw, userId, pkg);
}
}
@@ -828,6 +915,116 @@
pw.print(" mLastAppIdleParoledTime=");
TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
pw.println();
+ pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds));
+ pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds));
+ }
+
+ /**
+ * Injector for interaction with external code. Override methods to provide a mock
+ * implementation for tests.
+ * onBootPhase() must be called with at least the PHASE_SYSTEM_SERVICES_READY
+ */
+ static class Injector {
+
+ private final Context mContext;
+ private final Looper mLooper;
+ private IDeviceIdleController mDeviceIdleController;
+ private IBatteryStats mBatteryStats;
+ private PackageManagerInternal mPackageManagerInternal;
+ private DisplayManager mDisplayManager;
+ int mBootPhase;
+
+ Injector(Context context, Looper looper) {
+ mContext = context;
+ mLooper = looper;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ Looper getLooper() {
+ return mLooper;
+ }
+
+ void onBootPhase(int phase) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+ mBatteryStats = IBatteryStats.Stub.asInterface(
+ ServiceManager.getService(BatteryStats.SERVICE_NAME));
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ mDisplayManager = (DisplayManager) mContext.getSystemService(
+ Context.DISPLAY_SERVICE);
+ }
+ mBootPhase = phase;
+ }
+
+ int getBootPhase() {
+ return mBootPhase;
+ }
+
+ /**
+ * Returns the elapsed realtime since the device started. Override this
+ * to control the clock.
+ * @return elapsed realtime
+ */
+ long elapsedRealtime() {
+ return SystemClock.elapsedRealtime();
+ }
+
+ long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+ boolean isAppIdleEnabled() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableAutoPowerModes);
+ }
+
+ boolean isCharging() {
+ return mContext.getSystemService(BatteryManager.class).isCharging();
+ }
+
+ boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
+ return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName);
+ }
+
+ File getDataSystemDirectory() {
+ return Environment.getDataSystemDirectory();
+ }
+
+ void noteEvent(int event, String packageName, int uid) throws RemoteException {
+ mBatteryStats.noteEvent(event, packageName, uid);
+ }
+
+ boolean isPackageEphemeral(int userId, String packageName) {
+ return mPackageManagerInternal.isPackageEphemeral(userId, packageName);
+ }
+
+ int[] getRunningUserIds() throws RemoteException {
+ return ActivityManager.getService().getRunningUserIds();
+ }
+
+ boolean isDefaultDisplayOn() {
+ return mDisplayManager
+ .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+ }
+
+ void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) {
+ mDisplayManager.registerDisplayListener(listener, handler);
+ }
+
+ String getActiveNetworkScorer() {
+ NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService(
+ Context.NETWORK_SCORE_SERVICE);
+ return nsm.getActiveScorerPackage();
+ }
+
+ public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName,
+ int userId) {
+ return appWidgetManager.isBoundWidgetPackage(packageName, userId);
+ }
}
class AppStandbyHandler extends Handler {
@@ -839,6 +1036,10 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
+ case MSG_INFORM_LISTENERS:
+ informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
+ break;
+
case MSG_FORCE_IDLE_STATE:
forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
break;
@@ -911,7 +1112,7 @@
if (displayId == Display.DEFAULT_DISPLAY) {
final boolean displayOn = isDisplayOn();
synchronized (mAppIdleLock) {
- mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime());
+ mAppIdleHistory.updateDisplay(displayOn, mInjector.elapsedRealtime());
}
}
}
@@ -931,6 +1132,8 @@
private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
private static final String KEY_PAROLE_INTERVAL = "parole_interval";
private static final String KEY_PAROLE_DURATION = "parole_duration";
+ private static final String KEY_SCREEN_TIME_THRESHOLDS = "screen_thresholds";
+ private static final String KEY_ELAPSED_TIME_THRESHOLDS = "elapsed_thresholds";
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -969,7 +1172,7 @@
COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
- COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
+ COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
// Default: 24 hours between paroles
mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
@@ -979,9 +1182,35 @@
COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
mAppIdleScreenThresholdMillis);
+
+ String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null);
+ mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
+ SCREEN_TIME_THRESHOLDS);
+
+ String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS, null);
+ mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue,
+ ELAPSED_TIME_THRESHOLDS);
+ }
+ }
+
+ long[] parseLongArray(String values, long[] defaults) {
+ if (values == null) return defaults;
+ if (values.isEmpty()) {
+ // Reset to defaults
+ return defaults;
+ } else {
+ String[] thresholds = values.split("/");
+ if (thresholds.length == THRESHOLD_BUCKETS.length) {
+ long[] array = new long[THRESHOLD_BUCKETS.length];
+ for (int i = 0; i < THRESHOLD_BUCKETS.length; i++) {
+ array[i] = Long.parseLong(thresholds[i]);
+ }
+ return array;
+ } else {
+ return defaults;
+ }
}
}
}
-
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index afafea1..44e6a6c 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -20,6 +20,7 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.IUidObserver;
+import android.app.usage.AppStandby;
import android.app.usage.ConfigurationStats;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
@@ -469,8 +470,32 @@
void dump(String[] args, PrintWriter pw) {
synchronized (mLock) {
IndentingPrintWriter idpw = new IndentingPrintWriter(pw, " ");
- ArraySet<String> argSet = new ArraySet<>();
- argSet.addAll(Arrays.asList(args));
+
+ boolean checkin = false;
+ boolean history = false;
+ String pkg = null;
+
+ if (args != null) {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ if ("--checkin".equals(arg)) {
+ checkin = true;
+ } else if ("--history".equals(arg)) {
+ history = true;
+ } else if ("history".equals(arg)) {
+ history = true;
+ break;
+ } else if ("flush".equals(arg)) {
+ flushToDiskLocked();
+ pw.println("Flushed stats to disk");
+ return;
+ } else {
+ // Anything else is a pkg to filter
+ pkg = arg;
+ break;
+ }
+ }
+ }
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
@@ -478,26 +503,23 @@
idpw.printPair("user", userId);
idpw.println();
idpw.increaseIndent();
- if (argSet.contains("--checkin")) {
+ if (checkin) {
mUserState.valueAt(i).checkin(idpw);
} else {
- mUserState.valueAt(i).dump(idpw);
+ mUserState.valueAt(i).dump(idpw, pkg);
idpw.println();
- if (args.length > 0) {
- if ("history".equals(args[0])) {
- mAppStandby.dumpHistory(idpw, userId);
- } else if ("flush".equals(args[0])) {
- flushToDiskLocked();
- pw.println("Flushed stats to disk");
- }
+ if (history) {
+ mAppStandby.dumpHistory(idpw, userId);
}
}
- mAppStandby.dumpUser(idpw, userId);
+ mAppStandby.dumpUser(idpw, userId, pkg);
idpw.decreaseIndent();
}
- pw.println();
- mAppStandby.dumpState(args, pw);
+ if (pkg == null) {
+ pw.println();
+ mAppStandby.dumpState(args, pw);
+ }
}
}
@@ -654,6 +676,55 @@
}
@Override
+ public int getAppStandbyBucket(String packageName, String callingPackage, int userId) {
+ if (!hasPermission(callingPackage)) {
+ throw new SecurityException("Don't have permission to query app standby bucket");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ try {
+ userId = ActivityManager.getService().handleIncomingUser(
+ Binder.getCallingPid(), callingUid, userId, false, true,
+ "getAppStandbyBucket", null);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(callingUid,
+ userId);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mAppStandby.getAppStandbyBucket(packageName, userId,
+ SystemClock.elapsedRealtime(), obfuscateInstantApps);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void setAppStandbyBucket(String packageName,
+ int bucket, int userId) {
+ getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE,
+ "No permission to change app standby state");
+
+ final int callingUid = Binder.getCallingUid();
+ try {
+ userId = ActivityManager.getService().handleIncomingUser(
+ Binder.getCallingPid(), callingUid, userId, false, true,
+ "setAppStandbyBucket", null);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mAppStandby.setAppStandbyBucket(packageName, userId, bucket,
+ AppStandby.REASON_PREDICTED + ":" + callingUid,
+ SystemClock.elapsedRealtime());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void whitelistAppTemporarily(String packageName, long duration, int userId)
throws RemoteException {
StringBuilder reason = new StringBuilder(32);
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 0abbb82..0b10590 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -474,19 +474,19 @@
mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
@Override
public boolean checkin(IntervalStats stats) {
- printIntervalStats(pw, stats, false);
+ printIntervalStats(pw, stats, false, null);
return true;
}
});
}
- void dump(IndentingPrintWriter pw) {
+ void dump(IndentingPrintWriter pw, String pkg) {
// This is not a check-in, only dump in-memory stats.
for (int interval = 0; interval < mCurrentStats.length; interval++) {
pw.print("In-memory ");
pw.print(intervalToString(interval));
pw.println(" stats");
- printIntervalStats(pw, mCurrentStats[interval], true);
+ printIntervalStats(pw, mCurrentStats[interval], true, pkg);
}
}
@@ -505,7 +505,7 @@
}
void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
- boolean prettyDates) {
+ boolean prettyDates, String pkg) {
if (prettyDates) {
pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
@@ -521,6 +521,9 @@
final int pkgCount = pkgStats.size();
for (int i = 0; i < pkgCount; i++) {
final UsageStats usageStats = pkgStats.valueAt(i);
+ if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
+ continue;
+ }
pw.printPair("package", usageStats.mPackageName);
pw.printPair("totalTime",
formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
@@ -533,6 +536,9 @@
pw.println("ChooserCounts");
pw.increaseIndent();
for (UsageStats usageStats : pkgStats.values()) {
+ if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
+ continue;
+ }
pw.printPair("package", usageStats.mPackageName);
if (usageStats.mChooserCounts != null) {
final int chooserCountSize = usageStats.mChooserCounts.size();
@@ -555,19 +561,22 @@
}
pw.decreaseIndent();
- pw.println("configurations");
- pw.increaseIndent();
- final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
- final int configCount = configStats.size();
- for (int i = 0; i < configCount; i++) {
- final ConfigurationStats config = configStats.valueAt(i);
- pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration));
- pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
- pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
- pw.printPair("count", config.mActivationCount);
- pw.println();
+ if (pkg == null) {
+ pw.println("configurations");
+ pw.increaseIndent();
+ final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
+ final int configCount = configStats.size();
+ for (int i = 0; i < configCount; i++) {
+ final ConfigurationStats config = configStats.valueAt(i);
+ pw.printPair("config", Configuration.resourceQualifierString(
+ config.mConfiguration));
+ pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
+ pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
+ pw.printPair("count", config.mActivationCount);
+ pw.println();
+ }
+ pw.decreaseIndent();
}
- pw.decreaseIndent();
pw.println("events");
pw.increaseIndent();
@@ -575,6 +584,9 @@
final int eventCount = events != null ? events.size() : 0;
for (int i = 0; i < eventCount; i++) {
final UsageEvents.Event event = events.valueAt(i);
+ if (pkg != null && !pkg.equals(event.mPackage)) {
+ continue;
+ }
pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
pw.printPair("type", eventToString(event.mEventType));
pw.printPair("package", event.mPackage);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index b040a63..7541b92 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -55,6 +55,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback {
@@ -162,13 +163,16 @@
mInfo.getServiceInfo().applicationInfo.uid, mHandler);
}
List<IBinder> activityTokens = null;
- if (activityToken == null) {
+ if (activityToken != null) {
+ activityTokens = new ArrayList<>();
+ activityTokens.add(activityToken);
+ } else {
// Let's get top activities from all visible stacks
activityTokens = LocalServices.getService(ActivityManagerInternal.class)
.getTopVisibleActivities();
}
return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
- activityToken, activityTokens);
+ activityTokens);
}
public boolean hideSessionLocked() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index d394d63..925219d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -16,6 +16,16 @@
package com.android.server.voiceinteraction;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_CONTENT;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_DATA;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
+import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.IActivityManager;
@@ -43,31 +53,24 @@
import android.service.voice.VoiceInteractionSession;
import android.util.Slog;
import android.view.IWindowManager;
-import android.view.WindowManager;
import com.android.internal.app.AssistUtils;
-import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.os.IResultReceiver;
import com.android.server.LocalServices;
+import com.android.server.am.AssistDataRequester;
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
-
-final class VoiceInteractionSessionConnection implements ServiceConnection {
+final class VoiceInteractionSessionConnection implements ServiceConnection,
+ AssistDataRequesterCallbacks {
final static String TAG = "VoiceInteractionServiceManager";
- private static final String KEY_RECEIVER_EXTRA_COUNT = "count";
- private static final String KEY_RECEIVER_EXTRA_INDEX = "index";
-
final IBinder mToken = new Binder();
final Object mLock;
final ComponentName mSessionComponentName;
@@ -90,27 +93,8 @@
IVoiceInteractionSessionService mService;
IVoiceInteractionSession mSession;
IVoiceInteractor mInteractor;
- boolean mHaveAssistData;
- int mPendingAssistDataCount;
- ArrayList<AssistDataForActivity> mAssistData = new ArrayList<>();
- boolean mHaveScreenshot;
- Bitmap mScreenshot;
ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
-
- static class AssistDataForActivity {
- int activityIndex;
- int activityCount;
- Bundle data;
-
- public AssistDataForActivity(Bundle data) {
- this.data = data;
- Bundle receiverExtras = data.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
- if (receiverExtras != null) {
- activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
- activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
- }
- }
- }
+ AssistDataRequester mAssistDataRequester;
IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@@ -146,32 +130,6 @@
}
};
- final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
- @Override
- public void send(int resultCode, Bundle resultData) throws RemoteException {
- synchronized (mLock) {
- if (mShown) {
- mHaveAssistData = true;
- mAssistData.add(new AssistDataForActivity(resultData));
- deliverSessionDataLocked();
- }
- }
- }
- };
-
- final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() {
- @Override
- public void send(Bitmap screenshot) throws RemoteException {
- synchronized (mLock) {
- if (mShown) {
- mHaveScreenshot = true;
- mScreenshot = screenshot;
- deliverSessionDataLocked();
- }
- }
- }
- };
-
public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user,
Context context, Callback callback, int callingUid, Handler handler) {
mLock = lock;
@@ -185,6 +143,9 @@
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
mAppOps = context.getSystemService(AppOpsManager.class);
+ mAssistDataRequester = new AssistDataRequester(mContext, mAm, mIWindowManager,
+ (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
+ this, mLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
IBinder permOwner = null;
try {
permOwner = mAm.newUriPermissionOwner("voicesession:"
@@ -224,8 +185,7 @@
}
public boolean showLocked(Bundle args, int flags, int disabledContext,
- IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken,
- List<IBinder> topActivities) {
+ IVoiceInteractionSessionShowCallback showCallback, List<IBinder> topActivities) {
if (mBound) {
if (!mFullyBound) {
mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
@@ -233,75 +193,18 @@
| Context.BIND_FOREGROUND_SERVICE,
new UserHandle(mUser));
}
+
mShown = true;
- boolean isAssistDataAllowed = true;
- try {
- isAssistDataAllowed = mAm.isAssistDataAllowedOnCurrentActivity();
- } catch (RemoteException e) {
- }
- disabledContext |= getUserDisabledShowContextLocked();
- boolean structureEnabled = isAssistDataAllowed
- && (disabledContext&VoiceInteractionSession.SHOW_WITH_ASSIST) == 0;
- boolean screenshotEnabled = isAssistDataAllowed && structureEnabled
- && (disabledContext&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0;
mShowArgs = args;
mShowFlags = flags;
- mHaveAssistData = false;
- mPendingAssistDataCount = 0;
- boolean needDisclosure = false;
- if ((flags&VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) {
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
- mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
- && structureEnabled) {
- mAssistData.clear();
- final int count = activityToken != null ? 1 : topActivities.size();
- for (int i = 0; i < count; i++) {
- IBinder topActivity = count == 1 ? activityToken : topActivities.get(i);
- try {
- MetricsLogger.count(mContext, "assist_with_context", 1);
- Bundle receiverExtras = new Bundle();
- receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i);
- receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, count);
- if (mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
- mAssistReceiver, receiverExtras, topActivity,
- /* focused= */ i == 0, /* newSessionId= */ i == 0)) {
- needDisclosure = true;
- mPendingAssistDataCount++;
- } else if (i == 0) {
- // Wasn't allowed... given that, let's not do the screenshot either.
- mHaveAssistData = true;
- mAssistData.clear();
- screenshotEnabled = false;
- break;
- }
- } catch (RemoteException e) {
- }
- }
- } else {
- mHaveAssistData = true;
- mAssistData.clear();
- }
- } else {
- mAssistData.clear();
- }
- mHaveScreenshot = false;
- if ((flags&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) != 0) {
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_SCREENSHOT, mCallingUid,
- mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
- && screenshotEnabled) {
- try {
- MetricsLogger.count(mContext, "assist_with_screen", 1);
- needDisclosure = true;
- mIWindowManager.requestAssistScreenshot(mScreenshotReceiver);
- } catch (RemoteException e) {
- }
- } else {
- mHaveScreenshot = true;
- mScreenshot = null;
- }
- } else {
- mScreenshot = null;
- }
+
+ mAssistDataRequester.requestAssistData(topActivities,
+ (disabledContext & VoiceInteractionSession.SHOW_WITH_ASSIST) == 0,
+ (disabledContext & VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0,
+ mCallingUid, mSessionComponentName.getPackageName());
+
+ boolean needDisclosure = mAssistDataRequester.getPendingDataCount() > 0
+ || mAssistDataRequester.getPendingScreenshotCount() > 0;
if (needDisclosure && AssistUtils.shouldDisclose(mContext, mSessionComponentName)) {
mHandler.post(mShowAssistDisclosureRunnable);
}
@@ -312,7 +215,6 @@
mShowFlags = 0;
} catch (RemoteException e) {
}
- deliverSessionDataLocked();
} else if (showCallback != null) {
mPendingShowCallbacks.add(showCallback);
}
@@ -328,6 +230,57 @@
return false;
}
+ @Override
+ public boolean canHandleReceivedAssistDataLocked() {
+ return mSession != null;
+ }
+
+ @Override
+ public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+ if (data == null) {
+ try {
+ mSession.handleAssist(null, null, null, 0, 0);
+ } catch (RemoteException e) {
+ // Can't happen
+ }
+ } else {
+ final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
+ final AssistStructure structure = data.getParcelable(ASSIST_KEY_STRUCTURE);
+ final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
+ final int uid = data.getInt(Intent.EXTRA_ASSIST_UID, -1);
+ if (uid >= 0 && content != null) {
+ Intent intent = content.getIntent();
+ if (intent != null) {
+ ClipData clipData = intent.getClipData();
+ if (clipData != null && Intent.isAccessUriMode(intent.getFlags())) {
+ grantClipDataPermissions(clipData, intent.getFlags(), uid,
+ mCallingUid, mSessionComponentName.getPackageName());
+ }
+ }
+ ClipData clipData = content.getClipData();
+ if (clipData != null) {
+ grantClipDataPermissions(clipData, FLAG_GRANT_READ_URI_PERMISSION,
+ uid, mCallingUid, mSessionComponentName.getPackageName());
+ }
+ }
+ try {
+ mSession.handleAssist(assistData, structure, content, activityIndex,
+ activityCount);
+ } catch (RemoteException e) {
+ // Can't happen
+ }
+ }
+ }
+
+ @Override
+ public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+ try {
+ mSession.handleScreenshot(screenshot);
+ } catch (RemoteException e) {
+ // Can't happen
+ }
+ }
+
void grantUriPermission(Uri uri, int mode, int srcUid, int destUid, String destPkg) {
if (!"content".equals(uri.getScheme())) {
return;
@@ -341,7 +294,7 @@
int sourceUserId = ContentProvider.getUserIdFromUri(uri, mUser);
uri = ContentProvider.getUriWithoutUserId(uri);
mAm.grantUriPermissionFromOwner(mPermissionOwner, srcUid, destPkg,
- uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
+ uri, FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
} catch (RemoteException e) {
} catch (SecurityException e) {
Slog.w(TAG, "Can't propagate permission", e);
@@ -370,89 +323,13 @@
}
}
- void deliverSessionDataLocked() {
- if (mSession == null) {
- return;
- }
- if (mHaveAssistData) {
- AssistDataForActivity assistData;
- if (mAssistData.isEmpty()) {
- // We're not actually going to get any data, deliver some nothing
- try {
- mSession.handleAssist(null, null, null, 0, 0);
- } catch (RemoteException e) {
- }
- } else {
- while (!mAssistData.isEmpty()) {
- if (mPendingAssistDataCount <= 0) {
- Slog.e(TAG, "mPendingAssistDataCount is " + mPendingAssistDataCount);
- }
- mPendingAssistDataCount--;
- assistData = mAssistData.remove(0);
- if (assistData.data == null) {
- try {
- mSession.handleAssist(null, null, null, assistData.activityIndex,
- assistData.activityCount);
- } catch (RemoteException e) {
- }
- } else {
- deliverSessionDataLocked(assistData);
- }
- }
- }
- if (mPendingAssistDataCount <= 0) {
- mHaveAssistData = false;
- } // else, more to come
- }
- if (mHaveScreenshot) {
- try {
- mSession.handleScreenshot(mScreenshot);
- } catch (RemoteException e) {
- }
- mScreenshot = null;
- mHaveScreenshot = false;
- }
- }
-
- private void deliverSessionDataLocked(AssistDataForActivity assistDataForActivity) {
- Bundle assistData = assistDataForActivity.data.getBundle(
- VoiceInteractionSession.KEY_DATA);
- AssistStructure structure = assistDataForActivity.data.getParcelable(
- VoiceInteractionSession.KEY_STRUCTURE);
- AssistContent content = assistDataForActivity.data.getParcelable(
- VoiceInteractionSession.KEY_CONTENT);
- int uid = assistDataForActivity.data.getInt(Intent.EXTRA_ASSIST_UID, -1);
- if (uid >= 0 && content != null) {
- Intent intent = content.getIntent();
- if (intent != null) {
- ClipData data = intent.getClipData();
- if (data != null && Intent.isAccessUriMode(intent.getFlags())) {
- grantClipDataPermissions(data, intent.getFlags(), uid,
- mCallingUid, mSessionComponentName.getPackageName());
- }
- }
- ClipData data = content.getClipData();
- if (data != null) {
- grantClipDataPermissions(data,
- Intent.FLAG_GRANT_READ_URI_PERMISSION,
- uid, mCallingUid, mSessionComponentName.getPackageName());
- }
- }
- try {
- mSession.handleAssist(assistData, structure, content,
- assistDataForActivity.activityIndex, assistDataForActivity.activityCount);
- } catch (RemoteException e) {
- }
- }
-
public boolean hideLocked() {
if (mBound) {
if (mShown) {
mShown = false;
mShowArgs = null;
mShowFlags = 0;
- mHaveAssistData = false;
- mAssistData.clear();
+ mAssistDataRequester.cancel();
mPendingShowCallbacks.clear();
if (mSession != null) {
try {
@@ -462,8 +339,7 @@
}
try {
mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
- Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION,
mUser);
} catch (RemoteException e) {
}
@@ -529,7 +405,7 @@
mShowFlags = 0;
} catch (RemoteException e) {
}
- deliverSessionDataLocked();
+ mAssistDataRequester.processPendingAssistData();
}
return true;
}
@@ -587,10 +463,7 @@
pw.print(prefix); pw.print("mSession="); pw.println(mSession);
pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
}
- pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
- if (mHaveAssistData) {
- pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
- }
+ mAssistDataRequester.dump(prefix, pw);
}
private Runnable mShowAssistDisclosureRunnable = new Runnable() {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index de980b2f..3fc2208 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -210,6 +210,12 @@
public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
/**
+ * Determine whether user can edit voicemail number in Settings.
+ */
+ public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL =
+ "editable_voicemail_number_setting_bool";
+
+ /**
* Since the default voicemail number is empty, if a SIM card does not have a voicemail number
* available the user cannot use voicemail. This flag allows the user to edit the voicemail
* number in such cases, and is false by default.
@@ -346,6 +352,17 @@
public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
/**
+ * Flag that specifies to use the user's own phone number as the voicemail number when there is
+ * no pre-loaded voicemail number on the SIM card.
+ * <p>
+ * {@link #KEY_DEFAULT_VM_NUMBER_STRING} takes precedence over this flag.
+ * <p>
+ * If false, the system default (*86) will be used instead.
+ */
+ public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL =
+ "config_telephony_use_own_number_for_voicemail_bool";
+
+ /**
* When {@code true}, changes to the mobile data enabled switch will not cause the VT
* registration state to change. That is, turning on or off mobile data will not cause VT to be
* enabled or disabled.
@@ -1181,24 +1198,21 @@
*/
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
- * CDMA_ROAMING_MODE_HOME = Home Networks
- * CDMA_ROAMING_MODE_AFFILIATED = Roaming on Affiliated networks
- * CDMA_ROAMING_MODE_ANY = Roaming on any networks
+ * The CDMA roaming mode (aka CDMA system select).
+ *
+ * <p>The value should be one of the CDMA_ROAMING_MODE_ constants in {@link TelephonyManager}.
+ * Values other than {@link TelephonyManager#CDMA_ROAMING_MODE_RADIO_DEFAULT} (which is the
+ * default) will take precedence over user selection.
+ *
+ * @see TelephonyManager#CDMA_ROAMING_MODE_RADIO_DEFAULT
+ * @see TelephonyManager#CDMA_ROAMING_MODE_HOME
+ * @see TelephonyManager#CDMA_ROAMING_MODE_AFFILIATED
+ * @see TelephonyManager#CDMA_ROAMING_MODE_ANY
*/
public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
- /** @hide */
- public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1;
- /** @hide */
- public static final int CDMA_ROAMING_MODE_HOME = 0;
- /** @hide */
- public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
- /** @hide */
- public static final int CDMA_ROAMING_MODE_ANY = 2;
+
+
/**
* Boolean indicating if support is provided for directly dialing FDN number from FDN list.
* If false, this feature is not supported.
@@ -1419,6 +1433,17 @@
"support_3gpp_call_forwarding_while_roaming_bool";
/**
+ * Boolean indicating whether to display voicemail number as default call forwarding number in
+ * call forwarding settings.
+ * If true, display vm number when cf number is null.
+ * If false, display the cf number from network.
+ * By default this value is false.
+ * @hide
+ */
+ public static final String KEY_DISPLAY_VOICEMAIL_NUMBER_AS_DEFAULT_CALL_FORWARDING_NUMBER_BOOL =
+ "display_voicemail_number_as_default_call_forwarding_number";
+
+ /**
* When {@code true}, the user will be notified when they attempt to place an international call
* when the call is placed using wifi calling.
* @hide
@@ -1518,6 +1543,13 @@
"boosted_lte_earfcns_string_array";
/**
+ * Determine whether to use only RSRP for the number of LTE signal bars.
+ * @hide
+ */
+ public static final String KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL =
+ "use_only_rsrp_for_lte_signal_bar_bool";
+
+ /**
* Key identifying if voice call barring notification is required to be shown to the user.
* @hide
*/
@@ -1604,6 +1636,13 @@
public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL =
"skip_cf_fail_to_disable_dialog_bool";
+ /**
+ * List of the FAC (feature access codes) to dial as a normal call.
+ * @hide
+ */
+ public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY =
+ "feature_access_codes_string_array";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1621,6 +1660,7 @@
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_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL, false);
sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true);
sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
@@ -1663,6 +1703,7 @@
sDefaults.putBoolean(KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL, true);
sDefaults.putBoolean(KEY_USE_HFA_FOR_PROVISIONING_BOOL, false);
+ sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL, true);
sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL, false);
sDefaults.putBoolean(KEY_USE_OTASP_FOR_PROVISIONING_BOOL, false);
sDefaults.putBoolean(KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL, false);
@@ -1796,7 +1837,8 @@
sDefaults.putBoolean(KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL, true);
sDefaults.putBoolean(KEY_USE_RCS_PRESENCE_BOOL, false);
sDefaults.putBoolean(KEY_FORCE_IMEI_BOOL, false);
- sDefaults.putInt(KEY_CDMA_ROAMING_MODE_INT, CDMA_ROAMING_MODE_RADIO_DEFAULT);
+ sDefaults.putInt(
+ KEY_CDMA_ROAMING_MODE_INT, TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
sDefaults.putString(KEY_RCS_CONFIG_SERVER_URL_STRING, "");
// Carrier Signalling Receivers
@@ -1860,11 +1902,14 @@
sDefaults.putInt(KEY_EMERGENCY_NOTIFICATION_DELAY_INT, -1);
sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
+ sDefaults.putBoolean(KEY_DISPLAY_VOICEMAIL_NUMBER_AS_DEFAULT_CALL_FORWARDING_NUMBER_BOOL,
+ false);
sDefaults.putBoolean(KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL, false);
sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY,
null);
sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null);
+ sDefaults.putBoolean(KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false);
sDefaults.putInt(IMSI_KEY_AVAILABILITY_INT, 0);
sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null);
@@ -1874,6 +1919,7 @@
sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
+ sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
}
/**
diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java
index b39b4c7..ddc938e 100644
--- a/telephony/java/android/telephony/CellIdentityCdma.java
+++ b/telephony/java/android/telephony/CellIdentityCdma.java
@@ -19,6 +19,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -50,6 +51,10 @@
* to +90 degrees).
*/
private final int mLatitude;
+ // long alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaLong;
+ // short alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaShort;
/**
* @hide
@@ -60,6 +65,8 @@
mBasestationId = Integer.MAX_VALUE;
mLongitude = Integer.MAX_VALUE;
mLatitude = Integer.MAX_VALUE;
+ mAlphaLong = null;
+ mAlphaShort = null;
}
/**
@@ -75,19 +82,37 @@
* @hide
*/
public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat) {
+ this(nid, sid, bid, lon, lat, null, null);
+ }
+
+ /**
+ * public constructor
+ * @param nid Network Id 0..65535
+ * @param sid CDMA System Id 0..32767
+ * @param bid Base Station Id 0..65535
+ * @param lon Longitude is a decimal number ranges from -2592000
+ * to 2592000
+ * @param lat Latitude is a decimal number ranges from -1296000
+ * to 1296000
+ * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+ * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+ *
+ * @hide
+ */
+ public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat, String alphal,
+ String alphas) {
mNetworkId = nid;
mSystemId = sid;
mBasestationId = bid;
mLongitude = lon;
mLatitude = lat;
+ mAlphaLong = alphal;
+ mAlphaShort = alphas;
}
private CellIdentityCdma(CellIdentityCdma cid) {
- mNetworkId = cid.mNetworkId;
- mSystemId = cid.mSystemId;
- mBasestationId = cid.mBasestationId;
- mLongitude = cid.mLongitude;
- mLatitude = cid.mLatitude;
+ this(cid.mNetworkId, cid.mSystemId, cid.mBasestationId, cid.mLongitude, cid.mLatitude,
+ cid.mAlphaLong, cid.mAlphaShort);
}
CellIdentityCdma copy() {
@@ -137,9 +162,26 @@
return mLatitude;
}
+ /**
+ * @return The long alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaLong() {
+ return mAlphaLong;
+ }
+
+ /**
+ * @return The short alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaShort() {
+ return mAlphaShort;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude);
+ return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude,
+ mAlphaLong, mAlphaShort);
}
@Override
@@ -153,11 +195,14 @@
}
CellIdentityCdma o = (CellIdentityCdma) other;
+
return mNetworkId == o.mNetworkId &&
mSystemId == o.mSystemId &&
mBasestationId == o.mBasestationId &&
mLatitude == o.mLatitude &&
- mLongitude == o.mLongitude;
+ mLongitude == o.mLongitude &&
+ TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+ TextUtils.equals(mAlphaShort, o.mAlphaShort);
}
@Override
@@ -168,6 +213,8 @@
sb.append(" mBasestationId="); sb.append(mBasestationId);
sb.append(" mLongitude="); sb.append(mLongitude);
sb.append(" mLatitude="); sb.append(mLatitude);
+ sb.append(" mAlphaLong="); sb.append(mAlphaLong);
+ sb.append(" mAlphaShort="); sb.append(mAlphaShort);
sb.append("}");
return sb.toString();
@@ -188,15 +235,15 @@
dest.writeInt(mBasestationId);
dest.writeInt(mLongitude);
dest.writeInt(mLatitude);
+ dest.writeString(mAlphaLong);
+ dest.writeString(mAlphaShort);
}
/** Construct from Parcel, type has already been processed */
private CellIdentityCdma(Parcel in) {
- mNetworkId = in.readInt();
- mSystemId = in.readInt();
- mBasestationId = in.readInt();
- mLongitude = in.readInt();
- mLatitude = in.readInt();
+ this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readInt(),
+ in.readString(), in.readString());
+
if (DBG) log("CellIdentityCdma(Parcel): " + toString());
}
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index ec008e2..6276626 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -19,6 +19,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -30,10 +31,6 @@
private static final String LOG_TAG = "CellIdentityGsm";
private static final boolean DBG = false;
- // 3-digit Mobile Country Code, 0..999
- private final int mMcc;
- // 2 or 3-digit Mobile Network Code, 0..999
- private final int mMnc;
// 16-bit Location Area Code, 0..65535
private final int mLac;
// 16-bit GSM Cell Identity described in TS 27.007, 0..65535
@@ -42,17 +39,27 @@
private final int mArfcn;
// 6-bit Base Station Identity Code
private final int mBsic;
+ // 3-digit Mobile Country Code in string format
+ private final String mMccStr;
+ // 2 or 3-digit Mobile Network Code in string format
+ private final String mMncStr;
+ // long alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaLong;
+ // short alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaShort;
/**
* @hide
*/
public CellIdentityGsm() {
- mMcc = Integer.MAX_VALUE;
- mMnc = Integer.MAX_VALUE;
mLac = Integer.MAX_VALUE;
mCid = Integer.MAX_VALUE;
mArfcn = Integer.MAX_VALUE;
mBsic = Integer.MAX_VALUE;
+ mMccStr = null;
+ mMncStr = null;
+ mAlphaLong = null;
+ mAlphaShort = null;
}
/**
* public constructor
@@ -64,7 +71,8 @@
* @hide
*/
public CellIdentityGsm (int mcc, int mnc, int lac, int cid) {
- this(mcc, mnc, lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ this(lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE,
+ String.valueOf(mcc), String.valueOf(mnc), null, null);
}
/**
@@ -79,39 +87,81 @@
* @hide
*/
public CellIdentityGsm (int mcc, int mnc, int lac, int cid, int arfcn, int bsic) {
- mMcc = mcc;
- mMnc = mnc;
+ this(lac, cid, arfcn, bsic, String.valueOf(mcc), String.valueOf(mnc), null, null);
+ }
+
+ /**
+ * public constructor
+ * @param lac 16-bit Location Area Code, 0..65535
+ * @param cid 16-bit GSM Cell Identity or 28-bit UMTS Cell Identity
+ * @param arfcn 16-bit GSM Absolute RF Channel Number
+ * @param bsic 6-bit Base Station Identity Code
+ * @param mccStr 3-digit Mobile Country Code in string format
+ * @param mncStr 2 or 3-digit Mobile Network Code in string format
+ * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+ * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+ *
+ * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+ * not a 2 or 3-digit code.
+ *
+ * @hide
+ */
+ public CellIdentityGsm (int lac, int cid, int arfcn, int bsic, String mccStr,
+ String mncStr, String alphal, String alphas) {
mLac = lac;
mCid = cid;
mArfcn = arfcn;
- mBsic = bsic;
+ // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator
+ // for inbound parcels
+ mBsic = (bsic == 0xFF) ? Integer.MAX_VALUE : bsic;
+
+ if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+ mMccStr = mccStr;
+ } else if (mccStr.isEmpty()) {
+ // If the mccStr parsed from Parcel is empty, set it as null.
+ mMccStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MCC format");
+ }
+
+ if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+ mMncStr = mncStr;
+ } else if (mncStr.isEmpty()) {
+ // If the mncStr parsed from Parcel is empty, set it as null.
+ mMncStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MNC format");
+ }
+
+ mAlphaLong = alphal;
+ mAlphaShort = alphas;
}
private CellIdentityGsm(CellIdentityGsm cid) {
- mMcc = cid.mMcc;
- mMnc = cid.mMnc;
- mLac = cid.mLac;
- mCid = cid.mCid;
- mArfcn = cid.mArfcn;
- mBsic = cid.mBsic;
+ this(cid.mLac, cid.mCid, cid.mArfcn, cid.mBsic, cid.mMccStr,
+ cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
}
CellIdentityGsm copy() {
- return new CellIdentityGsm(this);
+ return new CellIdentityGsm(this);
}
/**
* @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMccStr} instead.
*/
+ @Deprecated
public int getMcc() {
- return mMcc;
+ return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
}
/**
* @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMncStr} instead.
*/
+ @Deprecated
public int getMnc() {
- return mMnc;
+ return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
}
/**
@@ -144,6 +194,43 @@
return mBsic;
}
+ /**
+ * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+ */
+ public String getMobileNetworkOperator() {
+ return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+ }
+
+ /**
+ * @return Mobile Country Code in string format, null if unknown
+ */
+ public String getMccStr() {
+ return mMccStr;
+ }
+
+ /**
+ * @return Mobile Network Code in string format, null if unknown
+ */
+ public String getMncStr() {
+ return mMncStr;
+ }
+
+ /**
+ * @return The long alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaLong() {
+ return mAlphaLong;
+ }
+
+ /**
+ * @return The short alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaShort() {
+ return mAlphaShort;
+ }
+
/**
* @return Integer.MAX_VALUE, undefined for GSM
@@ -155,7 +242,7 @@
@Override
public int hashCode() {
- return Objects.hash(mMcc, mMnc, mLac, mCid);
+ return Objects.hash(mMccStr, mMncStr, mLac, mCid, mAlphaLong, mAlphaShort);
}
@Override
@@ -169,23 +256,27 @@
}
CellIdentityGsm o = (CellIdentityGsm) other;
- return mMcc == o.mMcc &&
- mMnc == o.mMnc &&
- mLac == o.mLac &&
+ return mLac == o.mLac &&
mCid == o.mCid &&
mArfcn == o.mArfcn &&
- mBsic == o.mBsic;
+ mBsic == o.mBsic &&
+ TextUtils.equals(mMccStr, o.mMccStr) &&
+ TextUtils.equals(mMncStr, o.mMncStr) &&
+ TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+ TextUtils.equals(mAlphaShort, o.mAlphaShort);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("CellIdentityGsm:{");
- sb.append(" mMcc=").append(mMcc);
- sb.append(" mMnc=").append(mMnc);
sb.append(" mLac=").append(mLac);
sb.append(" mCid=").append(mCid);
sb.append(" mArfcn=").append(mArfcn);
sb.append(" mBsic=").append("0x").append(Integer.toHexString(mBsic));
+ sb.append(" mMcc=").append(mMccStr);
+ sb.append(" mMnc=").append(mMncStr);
+ sb.append(" mAlphaLong=").append(mAlphaLong);
+ sb.append(" mAlphaShort=").append(mAlphaShort);
sb.append("}");
return sb.toString();
@@ -201,26 +292,20 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
if (DBG) log("writeToParcel(Parcel, int): " + toString());
- dest.writeInt(mMcc);
- dest.writeInt(mMnc);
dest.writeInt(mLac);
dest.writeInt(mCid);
dest.writeInt(mArfcn);
dest.writeInt(mBsic);
+ dest.writeString(mMccStr);
+ dest.writeString(mMncStr);
+ dest.writeString(mAlphaLong);
+ dest.writeString(mAlphaShort);
}
/** Construct from Parcel, type has already been processed */
private CellIdentityGsm(Parcel in) {
- mMcc = in.readInt();
- mMnc = in.readInt();
- mLac = in.readInt();
- mCid = in.readInt();
- mArfcn = in.readInt();
- int bsic = in.readInt();
- // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator
- // for inbound parcels
- if (bsic == 0xFF) bsic = Integer.MAX_VALUE;
- mBsic = bsic;
+ this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+ in.readString(), in.readString(), in.readString());
if (DBG) log("CellIdentityGsm(Parcel): " + toString());
}
@@ -229,16 +314,16 @@
@SuppressWarnings("hiding")
public static final Creator<CellIdentityGsm> CREATOR =
new Creator<CellIdentityGsm>() {
- @Override
- public CellIdentityGsm createFromParcel(Parcel in) {
- return new CellIdentityGsm(in);
- }
+ @Override
+ public CellIdentityGsm createFromParcel(Parcel in) {
+ return new CellIdentityGsm(in);
+ }
- @Override
- public CellIdentityGsm[] newArray(int size) {
- return new CellIdentityGsm[size];
- }
- };
+ @Override
+ public CellIdentityGsm[] newArray(int size) {
+ return new CellIdentityGsm[size];
+ }
+ };
/**
* log
@@ -246,4 +331,4 @@
private static void log(String s) {
Rlog.w(LOG_TAG, s);
}
-}
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index ce74383..74d2966 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -19,6 +19,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -30,10 +31,6 @@
private static final String LOG_TAG = "CellIdentityLte";
private static final boolean DBG = false;
- // 3-digit Mobile Country Code, 0..999
- private final int mMcc;
- // 2 or 3-digit Mobile Network Code, 0..999
- private final int mMnc;
// 28-bit cell identity
private final int mCi;
// physical cell id 0..503
@@ -42,17 +39,27 @@
private final int mTac;
// 18-bit Absolute RF Channel Number
private final int mEarfcn;
+ // 3-digit Mobile Country Code in string format
+ private final String mMccStr;
+ // 2 or 3-digit Mobile Network Code in string format
+ private final String mMncStr;
+ // long alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaLong;
+ // short alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaShort;
/**
* @hide
*/
public CellIdentityLte() {
- mMcc = Integer.MAX_VALUE;
- mMnc = Integer.MAX_VALUE;
mCi = Integer.MAX_VALUE;
mPci = Integer.MAX_VALUE;
mTac = Integer.MAX_VALUE;
mEarfcn = Integer.MAX_VALUE;
+ mMccStr = null;
+ mMncStr = null;
+ mAlphaLong = null;
+ mAlphaShort = null;
}
/**
@@ -66,7 +73,7 @@
* @hide
*/
public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac) {
- this(mcc, mnc, ci, pci, tac, Integer.MAX_VALUE);
+ this(ci, pci, tac, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), null, null);
}
/**
@@ -81,21 +88,57 @@
* @hide
*/
public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac, int earfcn) {
- mMcc = mcc;
- mMnc = mnc;
+ this(ci, pci, tac, earfcn, String.valueOf(mcc), String.valueOf(mnc), null, null);
+ }
+
+ /**
+ *
+ * @param ci 28-bit Cell Identity
+ * @param pci Physical Cell Id 0..503
+ * @param tac 16-bit Tracking Area Code
+ * @param earfcn 18-bit LTE Absolute RF Channel Number
+ * @param mccStr 3-digit Mobile Country Code in string format
+ * @param mncStr 2 or 3-digit Mobile Network Code in string format
+ * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+ * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+ *
+ * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+ * not a 2 or 3-digit code.
+ *
+ * @hide
+ */
+ public CellIdentityLte (int ci, int pci, int tac, int earfcn, String mccStr,
+ String mncStr, String alphal, String alphas) {
mCi = ci;
mPci = pci;
mTac = tac;
mEarfcn = earfcn;
+
+ if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+ mMccStr = mccStr;
+ } else if (mccStr.isEmpty()) {
+ // If the mccStr parsed from Parcel is empty, set it as null.
+ mMccStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MCC format");
+ }
+
+ if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+ mMncStr = mncStr;
+ } else if (mncStr.isEmpty()) {
+ // If the mncStr parsed from Parcel is empty, set it as null.
+ mMncStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MNC format");
+ }
+
+ mAlphaLong = alphal;
+ mAlphaShort = alphas;
}
private CellIdentityLte(CellIdentityLte cid) {
- mMcc = cid.mMcc;
- mMnc = cid.mMnc;
- mCi = cid.mCi;
- mPci = cid.mPci;
- mTac = cid.mTac;
- mEarfcn = cid.mEarfcn;
+ this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mMccStr,
+ cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
}
CellIdentityLte copy() {
@@ -104,16 +147,20 @@
/**
* @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMccStr} instead.
*/
+ @Deprecated
public int getMcc() {
- return mMcc;
+ return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
}
/**
* @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMncStr} instead.
*/
+ @Deprecated
public int getMnc() {
- return mMnc;
+ return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
}
/**
@@ -144,9 +191,46 @@
return mEarfcn;
}
+ /**
+ * @return Mobile Country Code in string format, null if unknown
+ */
+ public String getMccStr() {
+ return mMccStr;
+ }
+
+ /**
+ * @return Mobile Network Code in string format, null if unknown
+ */
+ public String getMncStr() {
+ return mMncStr;
+ }
+
+ /**
+ * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+ */
+ public String getMobileNetworkOperator() {
+ return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+ }
+
+ /**
+ * @return The long alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaLong() {
+ return mAlphaLong;
+ }
+
+ /**
+ * @return The short alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaShort() {
+ return mAlphaShort;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(mMcc, mMnc, mCi, mPci, mTac);
+ return Objects.hash(mMccStr, mMncStr, mCi, mPci, mTac, mAlphaLong, mAlphaShort);
}
@Override
@@ -160,23 +244,27 @@
}
CellIdentityLte o = (CellIdentityLte) other;
- return mMcc == o.mMcc &&
- mMnc == o.mMnc &&
- mCi == o.mCi &&
+ return mCi == o.mCi &&
mPci == o.mPci &&
mTac == o.mTac &&
- mEarfcn == o.mEarfcn;
+ mEarfcn == o.mEarfcn &&
+ TextUtils.equals(mMccStr, o.mMccStr) &&
+ TextUtils.equals(mMncStr, o.mMncStr) &&
+ TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+ TextUtils.equals(mAlphaShort, o.mAlphaShort);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("CellIdentityLte:{");
- sb.append(" mMcc="); sb.append(mMcc);
- sb.append(" mMnc="); sb.append(mMnc);
sb.append(" mCi="); sb.append(mCi);
sb.append(" mPci="); sb.append(mPci);
sb.append(" mTac="); sb.append(mTac);
sb.append(" mEarfcn="); sb.append(mEarfcn);
+ sb.append(" mMcc="); sb.append(mMccStr);
+ sb.append(" mMnc="); sb.append(mMncStr);
+ sb.append(" mAlphaLong="); sb.append(mAlphaLong);
+ sb.append(" mAlphaShort="); sb.append(mAlphaShort);
sb.append("}");
return sb.toString();
@@ -192,22 +280,21 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
if (DBG) log("writeToParcel(Parcel, int): " + toString());
- dest.writeInt(mMcc);
- dest.writeInt(mMnc);
dest.writeInt(mCi);
dest.writeInt(mPci);
dest.writeInt(mTac);
dest.writeInt(mEarfcn);
+ dest.writeString(mMccStr);
+ dest.writeString(mMncStr);
+ dest.writeString(mAlphaLong);
+ dest.writeString(mAlphaShort);
}
/** Construct from Parcel, type has already been processed */
private CellIdentityLte(Parcel in) {
- mMcc = in.readInt();
- mMnc = in.readInt();
- mCi = in.readInt();
- mPci = in.readInt();
- mTac = in.readInt();
- mEarfcn = in.readInt();
+ this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+ in.readString(), in.readString(), in.readString());
+
if (DBG) log("CellIdentityLte(Parcel): " + toString());
}
@@ -215,16 +302,16 @@
@SuppressWarnings("hiding")
public static final Creator<CellIdentityLte> CREATOR =
new Creator<CellIdentityLte>() {
- @Override
- public CellIdentityLte createFromParcel(Parcel in) {
- return new CellIdentityLte(in);
- }
+ @Override
+ public CellIdentityLte createFromParcel(Parcel in) {
+ return new CellIdentityLte(in);
+ }
- @Override
- public CellIdentityLte[] newArray(int size) {
- return new CellIdentityLte[size];
- }
- };
+ @Override
+ public CellIdentityLte[] newArray(int size) {
+ return new CellIdentityLte[size];
+ }
+ };
/**
* log
@@ -232,4 +319,4 @@
private static void log(String s) {
Rlog.w(LOG_TAG, s);
}
-}
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 0d13efd..51b11aa 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -19,6 +19,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -30,10 +31,6 @@
private static final String LOG_TAG = "CellIdentityWcdma";
private static final boolean DBG = false;
- // 3-digit Mobile Country Code, 0..999
- private final int mMcc;
- // 2 or 3-digit Mobile Network Code, 0..999
- private final int mMnc;
// 16-bit Location Area Code, 0..65535
private final int mLac;
// 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455
@@ -42,17 +39,27 @@
private final int mPsc;
// 16-bit UMTS Absolute RF Channel Number
private final int mUarfcn;
+ // 3-digit Mobile Country Code in string format
+ private final String mMccStr;
+ // 2 or 3-digit Mobile Network Code in string format
+ private final String mMncStr;
+ // long alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaLong;
+ // short alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaShort;
/**
* @hide
*/
public CellIdentityWcdma() {
- mMcc = Integer.MAX_VALUE;
- mMnc = Integer.MAX_VALUE;
mLac = Integer.MAX_VALUE;
mCid = Integer.MAX_VALUE;
mPsc = Integer.MAX_VALUE;
mUarfcn = Integer.MAX_VALUE;
+ mMccStr = null;
+ mMncStr = null;
+ mAlphaLong = null;
+ mAlphaShort = null;
}
/**
* public constructor
@@ -65,7 +72,8 @@
* @hide
*/
public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc) {
- this(mcc, mnc, lac, cid, psc, Integer.MAX_VALUE);
+ this(lac, cid, psc, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc),
+ null, null);
}
/**
@@ -80,39 +88,79 @@
* @hide
*/
public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc, int uarfcn) {
- mMcc = mcc;
- mMnc = mnc;
+ this(lac, cid, psc, uarfcn, String.valueOf(mcc), String.valueOf(mnc), null, null);
+ }
+
+ /**
+ * public constructor
+ * @param lac 16-bit Location Area Code, 0..65535
+ * @param cid 28-bit UMTS Cell Identity
+ * @param psc 9-bit UMTS Primary Scrambling Code
+ * @param uarfcn 16-bit UMTS Absolute RF Channel Number
+ * @param mccStr 3-digit Mobile Country Code in string format
+ * @param mncStr 2 or 3-digit Mobile Network Code in string format
+ * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+ * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+ *
+ * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+ * not a 2 or 3-digit code.
+ *
+ * @hide
+ */
+ public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn,
+ String mccStr, String mncStr, String alphal, String alphas) {
mLac = lac;
mCid = cid;
mPsc = psc;
mUarfcn = uarfcn;
+
+ if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+ mMccStr = mccStr;
+ } else if (mccStr.isEmpty()) {
+ // If the mccStr parsed from Parcel is empty, set it as null.
+ mMccStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MCC format");
+ }
+
+ if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+ mMncStr = mncStr;
+ } else if (mncStr.isEmpty()) {
+ // If the mncStr parsed from Parcel is empty, set it as null.
+ mMncStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MNC format");
+ }
+
+ mAlphaLong = alphal;
+ mAlphaShort = alphas;
}
private CellIdentityWcdma(CellIdentityWcdma cid) {
- mMcc = cid.mMcc;
- mMnc = cid.mMnc;
- mLac = cid.mLac;
- mCid = cid.mCid;
- mPsc = cid.mPsc;
- mUarfcn = cid.mUarfcn;
+ this(cid.mLac, cid.mCid, cid.mPsc, cid.mUarfcn, cid.mMccStr,
+ cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
}
CellIdentityWcdma copy() {
- return new CellIdentityWcdma(this);
+ return new CellIdentityWcdma(this);
}
/**
* @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMccStr} instead.
*/
+ @Deprecated
public int getMcc() {
- return mMcc;
+ return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
}
/**
* @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMncStr} instead.
*/
+ @Deprecated
public int getMnc() {
- return mMnc;
+ return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
}
/**
@@ -138,9 +186,46 @@
return mPsc;
}
+ /**
+ * @return Mobile Country Code in string version, null if unknown
+ */
+ public String getMccStr() {
+ return mMccStr;
+ }
+
+ /**
+ * @return Mobile Network Code in string version, null if unknown
+ */
+ public String getMncStr() {
+ return mMncStr;
+ }
+
+ /**
+ * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+ */
+ public String getMobileNetworkOperator() {
+ return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+ }
+
+ /**
+ * @return The long alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaLong() {
+ return mAlphaLong;
+ }
+
+ /**
+ * @return The short alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaShort() {
+ return mAlphaShort;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(mMcc, mMnc, mLac, mCid, mPsc);
+ return Objects.hash(mMccStr, mMncStr, mLac, mCid, mPsc, mAlphaLong, mAlphaShort);
}
/**
@@ -161,23 +246,27 @@
}
CellIdentityWcdma o = (CellIdentityWcdma) other;
- return mMcc == o.mMcc &&
- mMnc == o.mMnc &&
- mLac == o.mLac &&
+ return mLac == o.mLac &&
mCid == o.mCid &&
mPsc == o.mPsc &&
- mUarfcn == o.mUarfcn;
+ mUarfcn == o.mUarfcn &&
+ TextUtils.equals(mMccStr, o.mMccStr) &&
+ TextUtils.equals(mMncStr, o.mMncStr) &&
+ TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+ TextUtils.equals(mAlphaShort, o.mAlphaShort);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("CellIdentityWcdma:{");
- sb.append(" mMcc=").append(mMcc);
- sb.append(" mMnc=").append(mMnc);
sb.append(" mLac=").append(mLac);
sb.append(" mCid=").append(mCid);
sb.append(" mPsc=").append(mPsc);
sb.append(" mUarfcn=").append(mUarfcn);
+ sb.append(" mMcc=").append(mMccStr);
+ sb.append(" mMnc=").append(mMncStr);
+ sb.append(" mAlphaLong=").append(mAlphaLong);
+ sb.append(" mAlphaShort=").append(mAlphaShort);
sb.append("}");
return sb.toString();
@@ -193,22 +282,21 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
if (DBG) log("writeToParcel(Parcel, int): " + toString());
- dest.writeInt(mMcc);
- dest.writeInt(mMnc);
dest.writeInt(mLac);
dest.writeInt(mCid);
dest.writeInt(mPsc);
dest.writeInt(mUarfcn);
+ dest.writeString(mMccStr);
+ dest.writeString(mMncStr);
+ dest.writeString(mAlphaLong);
+ dest.writeString(mAlphaShort);
}
/** Construct from Parcel, type has already been processed */
private CellIdentityWcdma(Parcel in) {
- mMcc = in.readInt();
- mMnc = in.readInt();
- mLac = in.readInt();
- mCid = in.readInt();
- mPsc = in.readInt();
- mUarfcn = in.readInt();
+ this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+ in.readString(), in.readString(), in.readString());
+
if (DBG) log("CellIdentityWcdma(Parcel): " + toString());
}
@@ -216,16 +304,16 @@
@SuppressWarnings("hiding")
public static final Creator<CellIdentityWcdma> CREATOR =
new Creator<CellIdentityWcdma>() {
- @Override
- public CellIdentityWcdma createFromParcel(Parcel in) {
- return new CellIdentityWcdma(in);
- }
+ @Override
+ public CellIdentityWcdma createFromParcel(Parcel in) {
+ return new CellIdentityWcdma(in);
+ }
- @Override
- public CellIdentityWcdma[] newArray(int size) {
- return new CellIdentityWcdma[size];
- }
- };
+ @Override
+ public CellIdentityWcdma[] newArray(int size) {
+ return new CellIdentityWcdma[size];
+ }
+ };
/**
* log
@@ -233,4 +321,4 @@
private static void log(String s) {
Rlog.w(LOG_TAG, s);
}
-}
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 98fb653..c3a2ceb 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -273,6 +273,13 @@
* {@hide}
*/
public static final int EMERGENCY_PERM_FAILURE = 64;
+
+ /**
+ * This cause is used to report a normal event only when no other cause in the normal class
+ * applies.
+ * {@hide}
+ */
+ public static final int NORMAL_UNSPECIFIED = 65;
//*********************************************************************************************
// When adding a disconnect type:
// 1) Update toString() with the newly added disconnect type.
@@ -413,6 +420,8 @@
return "EMERGENCY_TEMP_FAILURE";
case EMERGENCY_PERM_FAILURE:
return "EMERGENCY_PERM_FAILURE";
+ case NORMAL_UNSPECIFIED:
+ return "NORMAL_UNSPECIFIED";
default:
return "INVALID: " + cause;
}
diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java
index 9a9877a..f392570 100644
--- a/telephony/java/android/telephony/MbmsDownloadSession.java
+++ b/telephony/java/android/telephony/MbmsDownloadSession.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -74,6 +75,14 @@
"android.telephony.action.EmbmsDownload";
/**
+ * Metadata key that specifies the component name of the service to bind to for file-download.
+ * @hide
+ */
+ @TestApi
+ public static final String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA =
+ "mbms-download-service-override";
+
+ /**
* Integer extra that Android will attach to the intent supplied via
* {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
* Indicates the result code of the download. One of
diff --git a/telephony/java/android/telephony/MbmsStreamingSession.java b/telephony/java/android/telephony/MbmsStreamingSession.java
index a8c4607..fb2ff7b 100644
--- a/telephony/java/android/telephony/MbmsStreamingSession.java
+++ b/telephony/java/android/telephony/MbmsStreamingSession.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
@@ -62,6 +63,14 @@
public static final String MBMS_STREAMING_SERVICE_ACTION =
"android.telephony.action.EmbmsStreaming";
+ /**
+ * Metadata key that specifies the component name of the service to bind to for file-download.
+ * @hide
+ */
+ @TestApi
+ public static final String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA =
+ "mbms-streaming-service-override";
+
private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index c8b4776..de02de7 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -68,6 +68,7 @@
private int mTdScdmaRscp;
private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult
+ private boolean mUseOnlyRsrpForLteLevel; // Use only RSRP for the number of LTE signal bar.
/**
* Create a new SignalStrength from a intent notifier Bundle
@@ -108,6 +109,7 @@
mLteRsrpBoost = 0;
mTdScdmaRscp = INVALID;
isGsm = true;
+ mUseOnlyRsrpForLteLevel = false;
}
/**
@@ -134,6 +136,7 @@
mLteRsrpBoost = 0;
mTdScdmaRscp = INVALID;
isGsm = gsmFlag;
+ mUseOnlyRsrpForLteLevel = false;
}
/**
@@ -145,10 +148,10 @@
int cdmaDbm, int cdmaEcio,
int evdoDbm, int evdoEcio, int evdoSnr,
int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
- int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag) {
+ int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag, boolean lteLevelBaseOnRsrp) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag);
+ lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag, lteLevelBaseOnRsrp);
mTdScdmaRscp = tdScdmaRscp;
}
@@ -164,7 +167,7 @@
int tdScdmaRscp, boolean gsmFlag) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
+ lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
mTdScdmaRscp = tdScdmaRscp;
}
@@ -180,7 +183,7 @@
boolean gsmFlag) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
+ lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
}
/**
@@ -194,7 +197,7 @@
boolean gsmFlag) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
- INVALID, INVALID, INVALID, 0, gsmFlag);
+ INVALID, INVALID, INVALID, 0, gsmFlag, false);
}
/**
@@ -228,7 +231,7 @@
boolean gsm) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
- INVALID, INVALID, INVALID, 0, gsm);
+ INVALID, INVALID, INVALID, 0, gsm, false);
}
/**
@@ -248,6 +251,7 @@
* @param lteCqi
* @param lteRsrpBoost
* @param gsm
+ * @param useOnlyRsrpForLteLevel
*
* @hide
*/
@@ -255,7 +259,7 @@
int cdmaDbm, int cdmaEcio,
int evdoDbm, int evdoEcio, int evdoSnr,
int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
- int lteRsrpBoost, boolean gsm) {
+ int lteRsrpBoost, boolean gsm, boolean useOnlyRsrpForLteLevel) {
mGsmSignalStrength = gsmSignalStrength;
mGsmBitErrorRate = gsmBitErrorRate;
mCdmaDbm = cdmaDbm;
@@ -271,6 +275,7 @@
mLteRsrpBoost = lteRsrpBoost;
mTdScdmaRscp = INVALID;
isGsm = gsm;
+ mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
if (DBG) log("initialize: " + toString());
}
@@ -293,6 +298,7 @@
mLteRsrpBoost = s.mLteRsrpBoost;
mTdScdmaRscp = s.mTdScdmaRscp;
isGsm = s.isGsm;
+ mUseOnlyRsrpForLteLevel = s.mUseOnlyRsrpForLteLevel;
}
/**
@@ -318,6 +324,7 @@
mLteRsrpBoost = in.readInt();
mTdScdmaRscp = in.readInt();
isGsm = (in.readInt() != 0);
+ mUseOnlyRsrpForLteLevel = (in.readInt() != 0);
}
/**
@@ -366,6 +373,7 @@
out.writeInt(mLteRsrpBoost);
out.writeInt(mTdScdmaRscp);
out.writeInt(isGsm ? 1 : 0);
+ out.writeInt(mUseOnlyRsrpForLteLevel ? 1 : 0);
}
/**
@@ -449,6 +457,17 @@
}
/**
+ * @param useOnlyRsrpForLteLevel true if it uses only RSRP for the number of LTE signal bar,
+ * otherwise false.
+ *
+ * Used by phone to use only RSRP or not for the number of LTE signal bar.
+ * @hide
+ */
+ public void setUseOnlyRsrpForLteLevel(boolean useOnlyRsrpForLteLevel) {
+ mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
+ }
+
+ /**
* @param lteRsrpBoost - signal strength offset
*
* Used by phone to set the lte signal strength offset which will be
@@ -835,6 +854,13 @@
}
}
+ if (useOnlyRsrpForLteLevel()) {
+ log("getLTELevel - rsrp = " + rsrpIconLevel);
+ if (rsrpIconLevel != -1) {
+ return rsrpIconLevel;
+ }
+ }
+
/*
* Values are -200 dB to +300 (SNR*10dB) RS_SNR >= 13.0 dB =>4 bars 4.5
* dB <= RS_SNR < 13.0 dB => 3 bars 1.0 dB <= RS_SNR < 4.5 dB => 2 bars
@@ -915,6 +941,15 @@
}
/**
+ * @return true if it uses only RSRP for the number of LTE signal bar, otherwise false.
+ *
+ * @hide
+ */
+ public boolean useOnlyRsrpForLteLevel() {
+ return this.mUseOnlyRsrpForLteLevel;
+ }
+
+ /**
* @return get TD_SCDMA dbm
*
* @hide
@@ -974,7 +1009,8 @@
+ (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum)
+ (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum)
+ (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum)
- + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0));
+ + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)
+ + (mUseOnlyRsrpForLteLevel ? 1 : 0));
}
/**
@@ -1008,7 +1044,8 @@
&& mLteCqi == s.mLteCqi
&& mLteRsrpBoost == s.mLteRsrpBoost
&& mTdScdmaRscp == s.mTdScdmaRscp
- && isGsm == s.isGsm);
+ && isGsm == s.isGsm
+ && mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel);
}
/**
@@ -1031,7 +1068,9 @@
+ " " + mLteCqi
+ " " + mLteRsrpBoost
+ " " + mTdScdmaRscp
- + " " + (isGsm ? "gsm|lte" : "cdma"));
+ + " " + (isGsm ? "gsm|lte" : "cdma")
+ + " " + (mUseOnlyRsrpForLteLevel ? "use_only_rsrp_for_lte_level" :
+ "use_rsrp_and_rssnr_for_lte_level"));
}
/** Returns the signal strength related to GSM. */
@@ -1086,6 +1125,7 @@
mLteRsrpBoost = m.getInt("lteRsrpBoost");
mTdScdmaRscp = m.getInt("TdScdma");
isGsm = m.getBoolean("isGsm");
+ mUseOnlyRsrpForLteLevel = m.getBoolean("useOnlyRsrpForLteLevel");
}
/**
@@ -1110,6 +1150,7 @@
m.putInt("lteRsrpBoost", mLteRsrpBoost);
m.putInt("TdScdma", mTdScdmaRscp);
m.putBoolean("isGsm", isGsm);
+ m.putBoolean("useOnlyRsrpForLteLevel", mUseOnlyRsrpForLteLevel);
}
/**
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 6029995..924f0de 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -387,23 +387,132 @@
}
/**
+ * Send a text based SMS with messaging options.
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param text the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values included Negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending messages through same link or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values included Negative considered as Invalid Validity Period of the message.
+ *
+ * @throws IllegalArgumentException if destinationAddress or text are empty
+ * {@hide}
+ */
+ public void sendTextMessage(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent,
+ int priority, boolean expectMore, int validityPeriod) {
+ sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+ true /* persistMessage*/, priority, expectMore, validityPeriod);
+ }
+
+ private void sendTextMessageInternal(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
+ int priority, boolean expectMore, int validityPeriod) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ if (priority < 0x00 || priority > 0x03) {
+ throw new IllegalArgumentException("Invalid priority");
+ }
+
+ if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+ throw new IllegalArgumentException("Invalid validity period");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ if (iccISms != null) {
+ iccISms.sendTextForSubscriberWithOptions(getSubscriptionId(),
+ ActivityThread.currentPackageName(), destinationAddress, scAddress, text,
+ sentIntent, deliveryIntent, persistMessage, priority, expectMore,
+ validityPeriod);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Send a text based SMS without writing it into the SMS Provider.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+ * privileges.
+ * </p>
+ *
+ * @see #sendTextMessage(String, String, String, PendingIntent,
+ * PendingIntent, int, boolean, int)
+ * @hide
+ */
+ public void sendTextMessageWithoutPersisting(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, int priority,
+ boolean expectMore, int validityPeriod) {
+ sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+ false /* persistMessage */, priority, expectMore, validityPeriod);
+ }
+
+ /**
+ *
* Inject an SMS PDU into the android application framework.
*
* <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
- * privileges. @see android.telephony.TelephonyManager#hasCarrierPrivileges
+ * privileges per {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
*
* @param pdu is the byte array of pdu to be injected into android application framework
- * @param format is the format of SMS pdu (3gpp or 3gpp2)
+ * @param format is the format of SMS pdu ({@link SmsMessage#FORMAT_3GPP} or
+ * {@link SmsMessage#FORMAT_3GPP2})
* @param receivedIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully received by the
* android application framework, or failed. This intent is broadcasted at
* the same time an SMS received from radio is acknowledged back.
- * The result code will be <code>RESULT_SMS_HANDLED</code> for success, or
- * <code>RESULT_SMS_GENERIC_ERROR</code> for error.
+ * The result code will be {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_HANDLED}
+ * for success, or {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_GENERIC_ERROR} for
+ * error.
*
- * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2.
+ * @throws IllegalArgumentException if the format is invalid.
*/
- public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
+ public void injectSmsPdu(
+ byte[] pdu, @SmsMessage.Format String format, PendingIntent receivedIntent) {
if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) {
// Format must be either 3gpp or 3gpp2.
throw new IllegalArgumentException(
@@ -541,6 +650,140 @@
}
/**
+ * Send a multi-part text based SMS with messaging options. The callee should have already
+ * divided the message into correctly sized parts by calling
+ * <code>divideMessage</code>.
+ *
+ * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+ *
+ * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+ * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+ * writes messages sent using this method to the SMS Provider (the default SMS app is always
+ * responsible for writing its sent messages to the SMS Provider). For information about
+ * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param parts an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values included Negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending messages through same link or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values included Negative considered as Invalid Validity Period of the message.
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ * {@hide}
+ */
+ public void sendMultipartTextMessage(
+ String destinationAddress, String scAddress, ArrayList<String> parts,
+ ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
+ int priority, boolean expectMore, int validityPeriod) {
+ sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+ deliveryIntents, true /* persistMessage*/);
+ }
+
+ private void sendMultipartTextMessageInternal(
+ String destinationAddress, String scAddress, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+ boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+ if (parts == null || parts.size() < 1) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ if (priority < 0x00 || priority > 0x03) {
+ throw new IllegalArgumentException("Invalid priority");
+ }
+
+ if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+ throw new IllegalArgumentException("Invalid validity period");
+ }
+
+ if (parts.size() > 1) {
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ if (iccISms != null) {
+ iccISms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
+ ActivityThread.currentPackageName(), destinationAddress, scAddress,
+ parts, sentIntents, deliveryIntents, persistMessage, priority,
+ expectMore, validityPeriod);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ } else {
+ PendingIntent sentIntent = null;
+ PendingIntent deliveryIntent = null;
+ if (sentIntents != null && sentIntents.size() > 0) {
+ sentIntent = sentIntents.get(0);
+ }
+ if (deliveryIntents != null && deliveryIntents.size() > 0) {
+ deliveryIntent = deliveryIntents.get(0);
+ }
+ sendTextMessageInternal(destinationAddress, scAddress, parts.get(0),
+ sentIntent, deliveryIntent, persistMessage, priority, expectMore,
+ validityPeriod);
+ }
+ }
+
+ /**
+ * Send a multi-part text based SMS without writing it into the SMS Provider.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+ * privileges.
+ * </p>
+ *
+ * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList,
+ * ArrayList, int, boolean, int)
+ * @hide
+ **/
+ public void sendMultipartTextMessageWithoutPersisting(
+ String destinationAddress, String scAddress, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+ int priority, boolean expectMore, int validityPeriod) {
+ sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+ deliveryIntents, false /* persistMessage*/, priority, expectMore,
+ validityPeriod);
+ }
+
+ /**
* Send a data based SMS to a specific application port.
*
* <p class="note"><strong>Note:</strong> Using this method requires that your app has the
@@ -1003,7 +1246,7 @@
* <code>getAllMessagesFromIcc</code>
* @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
*/
- private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
+ private ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
if (records != null) {
int count = records.size();
@@ -1011,7 +1254,8 @@
SmsRawData data = records.get(i);
// List contains all records, including "free" records (null)
if (data != null) {
- SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes());
+ SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes(),
+ getSubscriptionId());
if (sms != null) {
messages.add(sms);
}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index dcdda86..a5d67c6 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -16,24 +16,25 @@
package android.telephony;
-import android.os.Binder;
-import android.os.Parcel;
+import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+
+import android.annotation.StringDef;
import android.content.res.Resources;
+import android.os.Binder;
import android.text.TextUtils;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
-import com.android.internal.telephony.Sms7BitEncodingTranslator;
-import java.lang.Math;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
-
/**
* A Short Message Service message.
@@ -81,15 +82,18 @@
*/
public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
+ /** @hide */
+ @StringDef({FORMAT_3GPP, FORMAT_3GPP2})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Format {}
+
/**
* Indicates a 3GPP format SMS message.
- * @hide pending API council approval
*/
public static final String FORMAT_3GPP = "3gpp";
/**
* Indicates a 3GPP2 format SMS message.
- * @hide pending API council approval
*/
public static final String FORMAT_3GPP2 = "3gpp2";
@@ -267,6 +271,31 @@
}
/**
+ * Create an SmsMessage from an SMS EF record.
+ *
+ * @param index Index of SMS record. This should be index in ArrayList
+ * returned by SmsManager.getAllMessagesFromSim + 1.
+ * @param data Record data.
+ * @param subId Subscription Id of the SMS
+ * @return An SmsMessage representing the record.
+ *
+ * @hide
+ */
+ public static SmsMessage createFromEfRecord(int index, byte[] data, int subId) {
+ SmsMessageBase wrappedMessage;
+
+ if (isCdmaVoice(subId)) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+ index, data);
+ } else {
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+ index, data);
+ }
+
+ return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null;
+ }
+
+ /**
* Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
* length in bytes (not hex chars) less the SMSC header
*
@@ -818,6 +847,7 @@
int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subId);
return (PHONE_TYPE_CDMA == activePhone);
}
+
/**
* Decide if the carrier supports long SMS.
* {@hide}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c0564c5..42c3de5 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -953,6 +953,27 @@
*/
public static final int USSD_ERROR_SERVICE_UNAVAIL = -2;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which leaves the roaming
+ * mode set to the radio default or to the user's preference if they've indicated one.
+ */
+ public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which only permits
+ * connections on home networks.
+ */
+ public static final int CDMA_ROAMING_MODE_HOME = 0;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on
+ * affiliated networks.
+ */
+ public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on
+ * any network.
+ */
+ public static final int CDMA_ROAMING_MODE_ANY = 2;
+
//
//
// Device Info
@@ -2145,13 +2166,16 @@
* @hide
*/
public String getSimOperatorNumeric() {
- int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+ int subId = mSubId;
if (!SubscriptionManager.isUsableSubIdValue(subId)) {
- subId = SubscriptionManager.getDefaultSmsSubscriptionId();
+ subId = SubscriptionManager.getDefaultDataSubscriptionId();
if (!SubscriptionManager.isUsableSubIdValue(subId)) {
- subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+ subId = SubscriptionManager.getDefaultSmsSubscriptionId();
if (!SubscriptionManager.isUsableSubIdValue(subId)) {
- subId = SubscriptionManager.getDefaultSubscriptionId();
+ subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+ if (!SubscriptionManager.isUsableSubIdValue(subId)) {
+ subId = SubscriptionManager.getDefaultSubscriptionId();
+ }
}
}
}
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
index d8d7f48..b30a3af 100644
--- a/telephony/java/android/telephony/mbms/FileServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -17,6 +17,7 @@
package android.telephony.mbms;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,6 +36,7 @@
/** @hide */
@SystemApi
+ @TestApi
public FileServiceInfo(Map<Locale, String> newNames, String newClassName,
List<Locale> newLocales, String newServiceId, Date start, Date end,
List<FileInfo> newFiles) {
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index d38d8a7..b4ad1d7 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -22,6 +22,8 @@
import android.content.ServiceConnection;
import android.content.pm.*;
import android.content.pm.ServiceInfo;
+import android.telephony.MbmsDownloadSession;
+import android.telephony.MbmsStreamingSession;
import android.util.Log;
import java.io.File;
@@ -48,24 +50,64 @@
return new ComponentName(ci.packageName, ci.name);
}
+ private static ComponentName getOverrideServiceName(Context context, String serviceAction) {
+ String metaDataKey = null;
+ switch (serviceAction) {
+ case MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION:
+ metaDataKey = MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA;
+ break;
+ case MbmsStreamingSession.MBMS_STREAMING_SERVICE_ACTION:
+ metaDataKey = MbmsStreamingSession.MBMS_STREAMING_SERVICE_OVERRIDE_METADATA;
+ break;
+ }
+ if (metaDataKey == null) {
+ return null;
+ }
+
+ ApplicationInfo appInfo;
+ try {
+ appInfo = context.getPackageManager()
+ .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ if (appInfo.metaData == null) {
+ return null;
+ }
+ String serviceComponent = appInfo.metaData.getString(metaDataKey);
+ if (serviceComponent == null) {
+ return null;
+ }
+ return ComponentName.unflattenFromString(serviceComponent);
+ }
+
public static ServiceInfo getMiddlewareServiceInfo(Context context, String serviceAction) {
// Query for the proper service
PackageManager packageManager = context.getPackageManager();
Intent queryIntent = new Intent();
queryIntent.setAction(serviceAction);
- List<ResolveInfo> downloadServices = packageManager.queryIntentServices(queryIntent,
- PackageManager.MATCH_SYSTEM_ONLY);
- if (downloadServices == null || downloadServices.size() == 0) {
- Log.w(LOG_TAG, "No download services found, cannot get service info");
+ ComponentName overrideService = getOverrideServiceName(context, serviceAction);
+ List<ResolveInfo> services;
+ if (overrideService == null) {
+ services = packageManager.queryIntentServices(queryIntent,
+ PackageManager.MATCH_SYSTEM_ONLY);
+ } else {
+ queryIntent.setComponent(overrideService);
+ services = packageManager.queryIntentServices(queryIntent,
+ PackageManager.MATCH_ALL);
+ }
+
+ if (services == null || services.size() == 0) {
+ Log.w(LOG_TAG, "No MBMS services found, cannot get service info");
return null;
}
- if (downloadServices.size() > 1) {
- Log.w(LOG_TAG, "More than one download service found, cannot get unique service");
+ if (services.size() > 1) {
+ Log.w(LOG_TAG, "More than one MBMS service found, cannot get unique service");
return null;
}
- return downloadServices.get(0).serviceInfo;
+ return services.get(0).serviceInfo;
}
public static int startBinding(Context context, String serviceAction,
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
index c704f34..ef2a14a 100644
--- a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
@@ -17,6 +17,7 @@
package android.telephony.mbms;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -41,6 +42,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public StreamingServiceInfo(Map<Locale, String> names, String className,
List<Locale> locales, String serviceId, Date start, Date end) {
super(names, className, locales, serviceId, start, end);
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index 9ccdd56..4fee3df 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
@@ -42,6 +43,7 @@
* @hide
*/
@SystemApi
+@TestApi
public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index a238153..db177c0 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
@@ -38,6 +39,7 @@
* @hide
*/
@SystemApi
+@TestApi
public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
/**
* Initialize streaming service for this app and subId, registering the listener.
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index fe37531..a4eb424 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -187,6 +187,57 @@
in PendingIntent deliveryIntent, in boolean persistMessage);
/**
+ * Send an SMS with options using Subscription Id.
+ *
+ * @param subId the subId on which the SMS has to be sent.
+ * @param destAddr the address to send the message to
+ * @param scAddr the SMSC to send the message through, or NULL for the
+ * default SMSC
+ * @param text the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is sucessfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ * @param persistMessageForNonDefaultSmsApp whether the sent message should
+ * be automatically persisted in the SMS db. It only affects messages sent
+ * by a non-default SMS app. Currently only the carrier app can set this
+ * parameter to false to skip auto message persistence.
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values included Negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending message is multi segmented or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values included Negative considered as Invalid Validity Period of the message.
+ */
+ void sendTextForSubscriberWithOptions(in int subId, String callingPkg, in String destAddr,
+ in String scAddr, in String text, in PendingIntent sentIntent,
+ in PendingIntent deliveryIntent, in boolean persistMessageForNonDefaultSmsApp,
+ in int priority, in boolean expectMore, in int validityPeriod);
+
+ /**
* Inject an SMS PDU into the android platform.
*
* @param subId the subId on which the SMS has to be injected.
@@ -234,6 +285,56 @@
in List<PendingIntent> deliveryIntents, in boolean persistMessageForNonDefaultSmsApp);
/**
+ * Send a multi-part text based SMS with options using Subscription Id.
+ *
+ * @param subId the subId on which the SMS has to be sent.
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param parts an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code>
+ * <code>RESULT_ERROR_RADIO_OFF</code>
+ * <code>RESULT_ERROR_NULL_PDU</code>.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ * @param persistMessageForNonDefaultSmsApp whether the sent message should
+ * be automatically persisted in the SMS db. It only affects messages sent
+ * by a non-default SMS app. Currently only the carrier app can set this
+ * parameter to false to skip auto message persistence.
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values included Negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending message is multi segmented or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values included Negative considered as Invalid Validity Period of the message.
+ */
+ void sendMultipartTextForSubscriberWithOptions(in int subId, String callingPkg,
+ in String destinationAddress, in String scAddress, in List<String> parts,
+ in List<PendingIntent> sentIntents, in List<PendingIntent> deliveryIntents,
+ in boolean persistMessageForNonDefaultSmsApp, in int priority, in boolean expectMore,
+ in int validityPeriod);
+
+ /**
* Enable reception of cell broadcast (SMS-CB) messages with the given
* message identifier and RAN type. The RAN type specify this message ID
* belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 7a53ef6..14c5f4b 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -99,6 +99,15 @@
private static final int RETURN_NO_ACK = 0;
private static final int RETURN_ACK = 1;
+ /**
+ * Supported priority modes for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
+ */
+ private static final int PRIORITY_NORMAL = 0x0;
+ private static final int PRIORITY_INTERACTIVE = 0x1;
+ private static final int PRIORITY_URGENT = 0x2;
+ private static final int PRIORITY_EMERGENCY = 0x3;
+
private SmsEnvelope mEnvelope;
private BearerData mBearerData;
@@ -211,6 +220,26 @@
*/
public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
boolean statusReportRequested, SmsHeader smsHeader) {
+ return getSubmitPdu(scAddr, destAddr, message, statusReportRequested, smsHeader, -1);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddr Service Centre address. Null means use default.
+ * @param destAddr Address of the recipient.
+ * @param message String representation of the message payload.
+ * @param statusReportRequested Indicates whether a report is requested for this message.
+ * @param smsHeader Array containing the data for the User Data Header, preceded
+ * by the Element Identifiers.
+ * @param priority Priority level of the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
+ boolean statusReportRequested, SmsHeader smsHeader, int priority) {
/**
* TODO(cleanup): Do we really want silent failure like this?
@@ -224,7 +253,7 @@
UserData uData = new UserData();
uData.payloadStr = message;
uData.userDataHeader = smsHeader;
- return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+ return privateGetSubmitPdu(destAddr, statusReportRequested, uData, priority);
}
/**
@@ -282,6 +311,22 @@
}
/**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address & port
+ *
+ * @param destAddr the address of the destination for the message
+ * @param userData the data for the message
+ * @param statusReportRequested Indicates whether a report is requested for this message.
+ * @param priority Priority level of the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
+ boolean statusReportRequested, int priority) {
+ return privateGetSubmitPdu(destAddr, statusReportRequested, userData, priority);
+ }
+
+ /**
* Note: This function is a GSM specific functionality which is not supported in CDMA mode.
*/
@Override
@@ -764,6 +809,15 @@
*/
private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
UserData userData) {
+ return privateGetSubmitPdu(destAddrStr, statusReportRequested, userData, -1);
+ }
+
+ /**
+ * Creates BearerData and Envelope from parameters for a Submit SMS.
+ * @return byte stream for SubmitPdu.
+ */
+ private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
+ UserData userData, int priority) {
/**
* TODO(cleanup): give this function a more meaningful name.
@@ -792,6 +846,10 @@
bearerData.userAckReq = false;
bearerData.readAckReq = false;
bearerData.reportReq = false;
+ if (priority >= PRIORITY_NORMAL && priority <= PRIORITY_EMERGENCY) {
+ bearerData.priorityIndicatorSet = true;
+ bearerData.priority = priority;
+ }
bearerData.userData = userData;
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 1ca19e0..4f5bfa9 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -89,6 +89,18 @@
private int mVoiceMailCount = 0;
+ private static final int VALIDITY_PERIOD_FORMAT_NONE = 0x00;
+ private static final int VALIDITY_PERIOD_FORMAT_ENHANCED = 0x01;
+ private static final int VALIDITY_PERIOD_FORMAT_RELATIVE = 0x02;
+ private static final int VALIDITY_PERIOD_FORMAT_ABSOLUTE = 0x03;
+
+ //Validity Period min - 5 mins
+ private static final int VALIDITY_PERIOD_MIN = 5;
+ //Validity Period max - 63 weeks
+ private static final int VALIDITY_PERIOD_MAX = 635040;
+
+ private static final int INVALID_VALIDITY_PERIOD = -1;
+
public static class SubmitPdu extends SubmitPduBase {
}
@@ -202,6 +214,45 @@
}
/**
+ * Get Encoded Relative Validty Period Value from Validity period in mins.
+ *
+ * @param validityPeriod Validity period in mins.
+ *
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * ||relValidityPeriod (TP-VP) || || validityPeriod ||
+ *
+ * 0 to 143 ---> (TP-VP + 1) x 5 minutes
+ *
+ * 144 to 167 ---> 12 hours + ((TP-VP -143) x 30 minutes)
+ *
+ * 168 to 196 ---> (TP-VP - 166) x 1 day
+ *
+ * 197 to 255 ---> (TP-VP - 192) x 1 week
+ *
+ * @return relValidityPeriod Encoded Relative Validity Period Value.
+ * @hide
+ */
+ public static int getRelativeValidityPeriod(int validityPeriod) {
+ int relValidityPeriod = INVALID_VALIDITY_PERIOD;
+
+ if (validityPeriod < VALIDITY_PERIOD_MIN || validityPeriod > VALIDITY_PERIOD_MAX) {
+ Rlog.e(LOG_TAG,"Invalid Validity Period" + validityPeriod);
+ return relValidityPeriod;
+ }
+
+ if (validityPeriod <= 720) {
+ relValidityPeriod = (validityPeriod / 5) - 1;
+ } else if (validityPeriod <= 1440) {
+ relValidityPeriod = ((validityPeriod - 720) / 30) + 143;
+ } else if (validityPeriod <= 43200) {
+ relValidityPeriod = (validityPeriod / 1440) + 166;
+ } else if (validityPeriod <= 635040) {
+ relValidityPeriod = (validityPeriod / 10080) + 192;
+ }
+ return relValidityPeriod;
+ }
+
+ /**
* Get an SMS-SUBMIT PDU for a destination address and a message
*
* @param scAddress Service Centre address. Null means use default.
@@ -236,6 +287,29 @@
String destinationAddress, String message,
boolean statusReportRequested, byte[] header, int encoding,
int languageTable, int languageShiftTable) {
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+ header, encoding, languageTable, languageShiftTable, -1);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message using the
+ * specified encoding.
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @param encoding Encoding defined by constants in
+ * com.android.internal.telephony.SmsConstants.ENCODING_*
+ * @param languageTable
+ * @param languageShiftTable
+ * @param validityPeriod Validity Period of the message in Minutes.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, byte[] header, int encoding,
+ int languageTable, int languageShiftTable, int validityPeriod) {
// Perform null parameter checks.
if (message == null || destinationAddress == null) {
@@ -272,8 +346,19 @@
}
SubmitPdu ret = new SubmitPdu();
- // MTI = SMS-SUBMIT, UDHI = header != null
- byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
+
+ int validityPeriodFormat = VALIDITY_PERIOD_FORMAT_NONE;
+ int relativeValidityPeriod = INVALID_VALIDITY_PERIOD;
+
+ // TP-Validity-Period-Format (TP-VPF) in 3GPP TS 23.040 V6.8.1 section 9.2.3.3
+ //bit 4:3 = 10 - TP-VP field present - relative format
+ if((relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod)) >= 0) {
+ validityPeriodFormat = VALIDITY_PERIOD_FORMAT_RELATIVE;
+ }
+
+ byte mtiByte = (byte)(0x01 | (validityPeriodFormat << 0x03) |
+ (header != null ? 0x40 : 0x00));
+
ByteArrayOutputStream bo = getSubmitPduHead(
scAddress, destinationAddress, mtiByte,
statusReportRequested, ret);
@@ -338,7 +423,11 @@
bo.write(0x08);
}
- // (no TP-Validity-Period)
+ if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) {
+ // ( TP-Validity-Period - relative format)
+ bo.write(relativeValidityPeriod);
+ }
+
bo.write(userData, 0, userData.length);
ret.encodedMessage = bo.toByteArray();
return ret;
@@ -388,6 +477,24 @@
}
/**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @param destinationAddress the address of the destination for the message
+ * @param statusReportRequested staus report of the message Requested
+ * @param validityPeriod Validity Period of the message in Minutes.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, int validityPeriod) {
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+ null, ENCODING_UNKNOWN, 0, 0, validityPeriod);
+ }
+
+ /**
* Get an SMS-SUBMIT PDU for a data message to a destination address & port
*
* @param scAddress Service Centre address. null == use default
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index 62d570c..99a82ad 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -567,4 +567,12 @@
} while (valueIndex < endIndex);
return result;
}
+
+ public static String getDecimalSubstring(String iccId) {
+ int position;
+ for (position = 0; position < iccId.length(); position ++) {
+ if (!Character.isDigit(iccId.charAt(position))) break;
+ }
+ return iccId.substring( 0, position );
+ }
}
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 060a518..29a95e6 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -106,9 +106,11 @@
android.test.mock.stubs \
LOCAL_SOURCE_FILES_ALL_GENERATED := true
+LOCAL_SDK_VERSION := current
# Make sure to run droiddoc first to generate the stub source files.
LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_runner_api_gen_stamp)
+android_test_runner_api_gen_stamp :=
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -203,6 +205,7 @@
# Make sure to run droiddoc first to generate the stub source files.
LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_gen_stamp)
+android_test_mock_gen_stamp :=
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -246,6 +249,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE := android.test.mock.sdk
+LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := android.test.mock.stubs
diff --git a/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java b/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java
index a5261d0..95eb5c9 100644
--- a/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java
+++ b/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java
@@ -17,13 +17,13 @@
package com.android.compatibilitytest;
import android.app.ActivityManager;
+import android.app.ActivityManager.ProcessErrorStateInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.IActivityController;
import android.app.IActivityManager;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.app.UiModeManager;
-import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -72,6 +72,8 @@
DROPBOX_TAGS.add("data_app_native_crash");
DROPBOX_TAGS.add("data_app_crash");
}
+ private static final int MAX_CRASH_SNIPPET_LINES = 20;
+ private static final int MAX_NUM_CRASH_SNIPPET = 3;
// time waiting for app to launch
private int mAppLaunchTimeout = 7000;
@@ -149,11 +151,18 @@
try {
checkDropbox(startTime, packageName);
if (mAppErrors.containsKey(packageName)) {
- StringBuilder message = new StringBuilder("Error detected for package: ")
+ StringBuilder message = new StringBuilder("Error(s) detected for package: ")
.append(packageName);
- for (String err : mAppErrors.get(packageName)) {
+ List<String> errors = mAppErrors.get(packageName);
+ for (int i = 0; i < MAX_NUM_CRASH_SNIPPET && i < errors.size(); i++) {
+ String err = errors.get(i);
message.append("\n\n");
- message.append(err);
+ // limit the size of each crash snippet
+ message.append(truncate(err, MAX_CRASH_SNIPPET_LINES));
+ }
+ if (errors.size() > MAX_NUM_CRASH_SNIPPET) {
+ message.append(String.format("\n... %d more errors omitted ...",
+ errors.size() - MAX_NUM_CRASH_SNIPPET));
}
Assert.fail(message.toString());
}
@@ -171,6 +180,28 @@
}
/**
+ * Truncate the text to at most the specified number of lines, and append a marker at the end
+ * when truncated
+ * @param text
+ * @param maxLines
+ * @return
+ */
+ private static String truncate(String text, int maxLines) {
+ String[] lines = text.split("\\r?\\n");
+ StringBuilder ret = new StringBuilder();
+ for (int i = 0; i < maxLines && i < lines.length; i++) {
+ ret.append(lines[i]);
+ ret.append('\n');
+ }
+ if (lines.length > maxLines) {
+ ret.append("... ");
+ ret.append(lines.length - maxLines);
+ ret.append(" more lines truncated ...\n");
+ }
+ return ret.toString();
+ }
+
+ /**
* Check dropbox for entries of interest regarding the specified process
* @param startTime if not 0, only check entries with timestamp later than the start time
* @param processName the process name to check for
@@ -255,7 +286,7 @@
} else {
errors = new ArrayList<>();
}
- errors.add(String.format("type: %s details:\n%s", errorType, errorInfo));
+ errors.add(String.format("### Type: %s, Details:\n%s", errorType, errorInfo));
mAppErrors.put(pkgName, errors);
}
diff --git a/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java b/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
new file mode 100644
index 0000000..39f59f1
--- /dev/null
+++ b/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.reset;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VersionedBroadcastListenerTest {
+ private static final String TAG = VersionedBroadcastListenerTest.class.getSimpleName();
+ private static final String ACTION_TEST = "action.test.happy.broadcasts";
+
+ @Mock private Context mContext;
+ private BroadcastInterceptingContext mServiceContext;
+ private Handler mHandler;
+ private VersionedBroadcastListener mListener;
+ private int mCallbackCount;
+
+ private void doCallback() { mCallbackCount++; }
+
+ private class MockContext extends BroadcastInterceptingContext {
+ MockContext(Context base) {
+ super(base);
+ }
+ }
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ reset(mContext);
+ mServiceContext = new MockContext(mContext);
+ mHandler = new Handler(Looper.myLooper());
+ mCallbackCount = 0;
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_TEST);
+ mListener = new VersionedBroadcastListener(
+ TAG, mServiceContext, mHandler, filter, (Intent intent) -> doCallback());
+ }
+
+ @After public void tearDown() throws Exception {
+ if (mListener != null) {
+ mListener.stopListening();
+ mListener = null;
+ }
+ }
+
+ private void sendBroadcast() {
+ final Intent intent = new Intent(ACTION_TEST);
+ mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ @Test
+ public void testBasicListening() {
+ assertEquals(0, mCallbackCount);
+ mListener.startListening();
+ for (int i = 0; i < 5; i++) {
+ sendBroadcast();
+ assertEquals(i+1, mCallbackCount);
+ }
+ mListener.stopListening();
+ }
+
+ @Test
+ public void testBroadcastsBeforeStartAreIgnored() {
+ assertEquals(0, mCallbackCount);
+ for (int i = 0; i < 5; i++) {
+ sendBroadcast();
+ assertEquals(0, mCallbackCount);
+ }
+
+ mListener.startListening();
+ sendBroadcast();
+ assertEquals(1, mCallbackCount);
+ }
+
+ @Test
+ public void testBroadcastsAfterStopAreIgnored() {
+ mListener.startListening();
+ sendBroadcast();
+ assertEquals(1, mCallbackCount);
+ mListener.stopListening();
+
+ for (int i = 0; i < 5; i++) {
+ sendBroadcast();
+ assertEquals(1, mCallbackCount);
+ }
+ }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index c2cb66d..27a29b6 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -104,6 +104,8 @@
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.connectivity.DefaultNetworkMetrics;
+import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
@@ -156,6 +158,9 @@
private MockNetworkAgent mEthernetNetworkAgent;
private Context mContext;
+ @Mock IpConnectivityMetrics.Logger mMetricsService;
+ @Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
+
// This class exists to test bindProcessToNetwork and getBoundNetworkForProcess. These methods
// do not go through ConnectivityService but talk to netd directly, so they don't automatically
// reflect the state of our test ConnectivityService.
@@ -805,6 +810,11 @@
return Context.ETHERNET_SERVICE.equals(name);
}
+ @Override
+ protected IpConnectivityMetrics.Logger metricsLogger() {
+ return mMetricsService;
+ }
+
public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
return mLastCreatedNetworkMonitor;
}
@@ -833,6 +843,9 @@
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
+ MockitoAnnotations.initMocks(this);
+ when(mMetricsService.defaultNetworkMetrics()).thenReturn(mDefaultNetworkMetrics);
+
// InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
// http://b/25897652 .
if (Looper.myLooper() == null) {
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index b4b8094..9e97d84b 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -206,7 +206,7 @@
eq(CRYPT_KEY),
anyInt(),
eq(""),
- isNull(),
+ eq(new byte[] {}),
eq(0),
anyInt(),
anyInt(),
@@ -227,7 +227,7 @@
eq(CRYPT_KEY),
anyInt(),
eq(""),
- isNull(),
+ eq(new byte[] {}),
eq(0),
anyInt(),
anyInt(),
@@ -256,10 +256,10 @@
anyLong(),
eq(TEST_SPI_OUT),
eq(""),
- isNull(),
+ eq(new byte[] {}),
eq(0),
eq(""),
- isNull(),
+ eq(new byte[] {}),
eq(0),
eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM),
eq(CRYPT_KEY),
@@ -277,10 +277,10 @@
anyLong(),
eq(TEST_SPI_IN),
eq(""),
- isNull(),
+ eq(new byte[] {}),
eq(0),
eq(""),
- isNull(),
+ eq(new byte[] {}),
eq(0),
eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM),
eq(CRYPT_KEY),
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index 83ee361..7b07038 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -21,6 +21,7 @@
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
@@ -174,6 +175,7 @@
mIpSecService.openUdpEncapsulationSocket(0, new Binder());
assertNotNull(udpEncapResp);
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+ assertNotEquals(0, udpEncapResp.port);
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
udpEncapResp.fileDescriptor.close();
}
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index 2624176..ad6ebf9 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -198,21 +198,20 @@
@Test
public void testDefaultNetworkEventSerialization() {
- ConnectivityMetricsEvent ev = describeIpEvent(
- aType(DefaultNetworkEvent.class),
- anInt(102),
- anIntArray(1, 2, 3),
- anInt(101),
- aBool(true),
- aBool(false));
+ DefaultNetworkEvent ev = new DefaultNetworkEvent();
+ ev.netId = 102;
+ ev.prevNetId = 101;
+ ev.transportTypes = new int[]{1, 2, 3};
+ ev.prevIPv4 = true;
+ ev.prevIPv6 = true;
String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
" link_layer: 0",
- " network_id: 0",
- " time_ms: 1",
+ " network_id: 102",
+ " time_ms: 0",
" transports: 0",
" default_network_event <",
" default_network_duration_ms: 0",
@@ -226,7 +225,7 @@
" previous_network_id <",
" network_id: 101",
" >",
- " previous_network_ip_support: 1",
+ " previous_network_ip_support: 3",
" transport_types: 1",
" transport_types: 2",
" transport_types: 3",
@@ -234,7 +233,7 @@
">",
"version: 2\n");
- verifySerialization(want, ev);
+ verifySerialization(want, IpConnectivityEventBuilder.toProto(ev));
}
@Test
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index a395c48..6c1decc 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -18,6 +18,7 @@
import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,6 +31,10 @@
import android.net.ConnectivityManager;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.metrics.ApfProgramEvent;
@@ -41,18 +46,22 @@
import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
-import android.system.OsConstants;
import android.os.Parcelable;
import android.support.test.runner.AndroidJUnit4;
+import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64;
+
+import com.android.internal.util.BitUtils;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
+
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -162,6 +171,144 @@
}
@Test
+ public void testDefaultNetworkEvents() throws Exception {
+ final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+ final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
+
+ NetworkAgentInfo[][] defaultNetworks = {
+ // nothing -> cell
+ {null, makeNai(100, 10, false, true, cell)},
+ // cell -> wifi
+ {makeNai(100, 50, true, true, cell), makeNai(101, 20, true, false, wifi)},
+ // wifi -> nothing
+ {makeNai(101, 60, true, false, wifi), null},
+ // nothing -> cell
+ {null, makeNai(102, 10, true, true, cell)},
+ // cell -> wifi
+ {makeNai(102, 50, true, true, cell), makeNai(103, 20, true, false, wifi)},
+ };
+
+ for (NetworkAgentInfo[] pair : defaultNetworks) {
+ mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(pair[1], pair[0]);
+ }
+
+ String want = String.join("\n",
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 100",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " final_score: 0",
+ " initial_score: 0",
+ " ip_support: 0",
+ " network_id <",
+ " network_id: 100",
+ " >",
+ " no_default_network_duration_ms: 0",
+ " previous_network_id <",
+ " network_id: 0",
+ " >",
+ " previous_network_ip_support: 0",
+ " transport_types: 0",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 101",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " final_score: 0",
+ " initial_score: 0",
+ " ip_support: 0",
+ " network_id <",
+ " network_id: 101",
+ " >",
+ " no_default_network_duration_ms: 0",
+ " previous_network_id <",
+ " network_id: 100",
+ " >",
+ " previous_network_ip_support: 3",
+ " transport_types: 1",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " final_score: 0",
+ " initial_score: 0",
+ " ip_support: 0",
+ " network_id <",
+ " network_id: 0",
+ " >",
+ " no_default_network_duration_ms: 0",
+ " previous_network_id <",
+ " network_id: 101",
+ " >",
+ " previous_network_ip_support: 1",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 102",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " final_score: 0",
+ " initial_score: 0",
+ " ip_support: 0",
+ " network_id <",
+ " network_id: 102",
+ " >",
+ " no_default_network_duration_ms: 0",
+ " previous_network_id <",
+ " network_id: 0",
+ " >",
+ " previous_network_ip_support: 0",
+ " transport_types: 0",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 103",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " final_score: 0",
+ " initial_score: 0",
+ " ip_support: 0",
+ " network_id <",
+ " network_id: 103",
+ " >",
+ " no_default_network_duration_ms: 0",
+ " previous_network_id <",
+ " network_id: 102",
+ " >",
+ " previous_network_ip_support: 3",
+ " transport_types: 1",
+ " >",
+ ">",
+ "version: 2\n");
+
+ verifySerialization(want, getdump("flush"));
+ }
+
+ @Test
public void testEndToEndLogging() throws Exception {
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
@@ -194,7 +341,6 @@
Parcelable[] events = {
new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
new DhcpClientEvent("SomeState", 192),
- new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
validationEv,
apfStats,
@@ -233,6 +379,13 @@
wakeupEvent("wlan0", 10008);
wakeupEvent("rmnet0", 1000);
+ final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+ final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
+ NetworkAgentInfo cellNai = makeNai(100, 50, false, true, cell);
+ NetworkAgentInfo wifiNai = makeNai(101, 60, true, false, wifi);
+ mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(cellNai, null);
+ mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(wifiNai, cellNai);
+
String want = String.join("\n",
"dropped_events: 0",
"events <",
@@ -264,30 +417,6 @@
" network_id: 0",
" time_ms: 300",
" transports: 0",
- " default_network_event <",
- " default_network_duration_ms: 0",
- " final_score: 0",
- " initial_score: 0",
- " ip_support: 0",
- " network_id <",
- " network_id: 102",
- " >",
- " no_default_network_duration_ms: 0",
- " previous_network_id <",
- " network_id: 101",
- " >",
- " previous_network_ip_support: 1",
- " transport_types: 1",
- " transport_types: 2",
- " transport_types: 3",
- " >",
- ">",
- "events <",
- " if_name: \"\"",
- " link_layer: 4",
- " network_id: 0",
- " time_ms: 400",
- " transports: 0",
" ip_provisioning_event <",
" event_type: 1",
" if_name: \"\"",
@@ -298,7 +427,7 @@
" if_name: \"\"",
" link_layer: 4",
" network_id: 0",
- " time_ms: 500",
+ " time_ms: 400",
" transports: 0",
" validation_probe_event <",
" latency_ms: 40730",
@@ -310,7 +439,7 @@
" if_name: \"\"",
" link_layer: 4",
" network_id: 0",
- " time_ms: 600",
+ " time_ms: 500",
" transports: 0",
" apf_statistics <",
" dropped_ras: 2",
@@ -331,7 +460,7 @@
" if_name: \"\"",
" link_layer: 4",
" network_id: 0",
- " time_ms: 700",
+ " time_ms: 600",
" transports: 0",
" ra_event <",
" dnssl_lifetime: -1",
@@ -344,6 +473,50 @@
">",
"events <",
" if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 100",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " final_score: 0",
+ " initial_score: 0",
+ " ip_support: 0",
+ " network_id <",
+ " network_id: 100",
+ " >",
+ " no_default_network_duration_ms: 0",
+ " previous_network_id <",
+ " network_id: 0",
+ " >",
+ " previous_network_ip_support: 0",
+ " transport_types: 0",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 101",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " final_score: 0",
+ " initial_score: 0",
+ " ip_support: 0",
+ " network_id <",
+ " network_id: 101",
+ " >",
+ " no_default_network_duration_ms: 0",
+ " previous_network_id <",
+ " network_id: 100",
+ " >",
+ " previous_network_ip_support: 2",
+ " transport_types: 1",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
" link_layer: 4",
" network_id: 100",
" time_ms: 0",
@@ -471,6 +644,26 @@
mNetdListener.onWakeupEvent(prefix, uid, uid, 0);
}
+ NetworkAgentInfo makeNai(int netId, int score, boolean ipv4, boolean ipv6, long transports) {
+ NetworkAgentInfo nai = mock(NetworkAgentInfo.class);
+ when(nai.network()).thenReturn(new Network(netId));
+ when(nai.getCurrentScore()).thenReturn(score);
+ nai.linkProperties = new LinkProperties();
+ nai.networkCapabilities = new NetworkCapabilities();
+ for (int t : BitUtils.unpackBits(transports)) {
+ nai.networkCapabilities.addTransportType(t);
+ }
+ if (ipv4) {
+ nai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.12/24"));
+ nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0")));
+ }
+ if (ipv6) {
+ nai.linkProperties.addLinkAddress(new LinkAddress("2001:db8:dead:beef:f00::a0/64"));
+ nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("::/0")));
+ }
+ return nai;
+ }
+
List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
ArgumentCaptor<ConnectivityMetricsEvent> captor =
ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
diff --git a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
index b5d333b..f58ea7e 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
@@ -48,8 +48,6 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SimChangeListenerTest {
- private static final int EVENT_UNM_UPDATE = 1;
-
@Mock private Context mContext;
private BroadcastInterceptingContext mServiceContext;
private Handler mHandler;
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index f6006b0..6435ad9 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -15,9 +15,11 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.testables">
+ package="com.android.testables" android:sharedUserId="android.uid.system">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <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" />
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 55a4c43..e0dae1b 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -96,6 +96,7 @@
Maybe<std::string> generate_text_symbols_path;
Maybe<std::string> generate_proguard_rules_path;
Maybe<std::string> generate_main_dex_proguard_rules_path;
+ bool generate_conditional_proguard_rules = false;
bool generate_non_final_ids = false;
std::vector<std::string> javadoc_annotations;
Maybe<std::string> private_symbols;
@@ -499,7 +500,7 @@
return {};
}
- if (options_.update_proguard_spec && !proguard::CollectProguardRules(src, doc, keep_set_)) {
+ if (options_.update_proguard_spec && !proguard::CollectProguardRules(doc, keep_set_)) {
return {};
}
@@ -538,6 +539,8 @@
bool error = false;
std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> config_sorted_files;
+ proguard::CollectResourceReferences(context_, table, keep_set_);
+
for (auto& pkg : table->packages) {
CHECK(!pkg->name.empty()) << "Packages must have names when being linked";
@@ -1719,7 +1722,8 @@
}
}
- proguard::KeepSet proguard_keep_set;
+ proguard::KeepSet proguard_keep_set =
+ proguard::KeepSet(options_.generate_conditional_proguard_rules);
proguard::KeepSet proguard_main_dex_keep_set;
if (context_->GetPackageType() == PackageType::kStaticLib) {
@@ -1795,14 +1799,12 @@
XmlReferenceLinker manifest_linker;
if (manifest_linker.Consume(context_, manifest_xml.get())) {
if (options_.generate_proguard_rules_path &&
- !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path),
- manifest_xml.get(), &proguard_keep_set)) {
+ !proguard::CollectProguardRulesForManifest(manifest_xml.get(), &proguard_keep_set)) {
error = true;
}
if (options_.generate_main_dex_proguard_rules_path &&
- !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path),
- manifest_xml.get(),
+ !proguard::CollectProguardRulesForManifest(manifest_xml.get(),
&proguard_main_dex_keep_set, true)) {
error = true;
}
@@ -1919,6 +1921,9 @@
.OptionalFlag("--proguard-main-dex",
"Output file for generated Proguard rules for the main dex.",
&options.generate_main_dex_proguard_rules_path)
+ .OptionalSwitch("--proguard-conditional-keep-rules",
+ "Generate conditional Proguard keep rules.",
+ &options.generate_conditional_proguard_rules)
.OptionalSwitch("--no-auto-version",
"Disables automatic style and layout SDK versioning.",
&options.no_auto_version)
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp
index a179260..8b6c524 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.cpp
+++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp
@@ -118,10 +118,11 @@
size_t name_suffix_counter = 0;
for (const InlineDeclaration& decl : visitor.GetInlineDeclarations()) {
- auto new_doc = util::make_unique<xml::XmlResource>();
- new_doc->file.config = doc->file.config;
- new_doc->file.source = doc->file.source.WithLine(decl.el->line_number);
- new_doc->file.name = doc->file.name;
+ // Create a new XmlResource with the same ResourceFile as the base XmlResource.
+ auto new_doc = util::make_unique<xml::XmlResource>(doc->file);
+
+ // Attach the line number.
+ new_doc->file.source.line = decl.el->line_number;
// Modify the new entry name. We need to suffix the entry with a number to
// avoid local collisions, then mangle it with the empty package, such that it won't show up
@@ -146,6 +147,10 @@
} else {
new_doc->root.reset(static_cast<xml::Element*>(child.release()));
new_doc->root->parent = nullptr;
+ // Copy down the namespace declarations
+ new_doc->root->namespace_decls = doc->root->namespace_decls;
+ // Recurse for nested inlines
+ Consume(context, new_doc.get());
}
}
diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
index de7739a..2b4ab96 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
+++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
@@ -54,6 +54,7 @@
</View1>)");
doc->file.name = test::ParseNameOrDie("layout/main");
+ doc->file.type = ResourceFile::Type::kProtoXml;
InlineXmlFormatParser parser;
ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
@@ -81,6 +82,9 @@
// Make sure the generated reference is correct.
EXPECT_THAT(extracted_doc->file.name, Eq(name_ref));
+ // Make sure the ResourceFile::Type is the same.
+ EXPECT_THAT(extracted_doc->file.type, Eq(ResourceFile::Type::kProtoXml));
+
// Verify the structure of the extracted XML.
el = extracted_doc->root.get();
ASSERT_THAT(el, NotNull());
@@ -137,4 +141,47 @@
EXPECT_THAT(extracted_doc_drawable->root->name, StrEq("vector"));
}
+TEST(InlineXmlFormatParserTest, ExtractNestedXmlResources) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
+ <base_root xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="inline_xml">
+ <inline_root>
+ <aapt:attr name="nested_inline_xml">
+ <nested_inline_root/>
+ </aapt:attr>
+ <aapt:attr name="another_nested_inline_xml">
+ <root/>
+ </aapt:attr>
+ </inline_root>
+ </aapt:attr>
+ <aapt:attr name="turtles">
+ <root1>
+ <aapt:attr name="all">
+ <root2>
+ <aapt:attr name="the">
+ <root3>
+ <aapt:attr name="way">
+ <root4>
+ <aapt:attr name="down">
+ <root5/>
+ </aapt:attr>
+ </root4>
+ </aapt:attr>
+ </root3>
+ </aapt:attr>
+ </root2>
+ </aapt:attr>
+ </root1>
+ </aapt:attr>
+ </base_root>)");
+
+ doc->file.name = test::ParseNameOrDie("layout/main");
+
+ InlineXmlFormatParser parser;
+ ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
+ // Confirm that all of the nested inline xmls are parsed out.
+ ASSERT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(8u));
+}
} // namespace aapt
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 8da9106..3ba4dd8 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -61,7 +61,7 @@
// Java symbols can not contain . or -, but those are valid in a resource name.
// Replace those with '_'.
-static std::string TransformToFieldName(const StringPiece& symbol) {
+std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) {
std::string output = symbol.to_string();
for (char& c : output) {
if (c == '.' || c == '-') {
@@ -89,9 +89,9 @@
// the package.
if (!attr_name.package.empty() &&
package_name_to_generate != attr_name.package) {
- output += "_" + TransformToFieldName(attr_name.package);
+ output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.package);
}
- output += "_" + TransformToFieldName(attr_name.entry);
+ output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.entry);
return output;
}
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 18746ff..2541749 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -24,6 +24,7 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
+#include "androidfw/StringPiece.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
@@ -78,6 +79,8 @@
const std::string& getError() const;
+ static std::string TransformToFieldName(const android::StringPiece& symbol);
+
private:
bool SkipSymbol(SymbolState state);
bool SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol);
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 10c4610..b9ae654 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -21,6 +21,10 @@
#include "android-base/macros.h"
+#include "JavaClassGenerator.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
+#include "androidfw/StringPiece.h"
#include "util/Util.h"
#include "xml/XmlDom.h"
@@ -31,7 +35,7 @@
public:
using xml::Visitor::Visit;
- BaseVisitor(const Source& source, KeepSet* keep_set) : source_(source), keep_set_(keep_set) {
+ BaseVisitor(const ResourceFile& file, KeepSet* keep_set) : file_(file), keep_set_(keep_set) {
}
void Visit(xml::Element* node) override {
@@ -52,27 +56,47 @@
for (const auto& child : node->children) {
child->Accept(this);
}
+
+ for (const auto& attr : node->attributes) {
+ if (attr.compiled_value) {
+ auto ref = ValueCast<Reference>(attr.compiled_value.get());
+ if (ref) {
+ AddReference(node->line_number, ref);
+ }
+ }
+ }
}
protected:
- void AddClass(size_t line_number, const std::string& class_name) {
- keep_set_->AddClass(Source(source_.path, line_number), class_name);
+ ResourceFile file_;
+ KeepSet* keep_set_;
+
+ virtual void AddClass(size_t line_number, const std::string& class_name) {
+ keep_set_->AddConditionalClass({file_.name, file_.source.WithLine(line_number)}, class_name);
}
void AddMethod(size_t line_number, const std::string& method_name) {
- keep_set_->AddMethod(Source(source_.path, line_number), method_name);
+ keep_set_->AddMethod({file_.name, file_.source.WithLine(line_number)}, method_name);
+ }
+
+ void AddReference(size_t line_number, Reference* ref) {
+ if (ref && ref->name) {
+ ResourceName ref_name = ref->name.value();
+ if (ref_name.package.empty()) {
+ ref_name = ResourceName(file_.name.package, ref_name.type, ref_name.entry);
+ }
+ keep_set_->AddReference({file_.name, file_.source.WithLine(line_number)}, ref_name);
+ }
}
private:
DISALLOW_COPY_AND_ASSIGN(BaseVisitor);
- Source source_;
- KeepSet* keep_set_;
};
class LayoutVisitor : public BaseVisitor {
public:
- LayoutVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
+ LayoutVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
}
void Visit(xml::Element* node) override {
@@ -110,7 +134,7 @@
class MenuVisitor : public BaseVisitor {
public:
- MenuVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
+ MenuVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
}
void Visit(xml::Element* node) override {
@@ -136,7 +160,7 @@
class XmlResourceVisitor : public BaseVisitor {
public:
- XmlResourceVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
+ XmlResourceVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
}
void Visit(xml::Element* node) override {
@@ -163,7 +187,7 @@
class TransitionVisitor : public BaseVisitor {
public:
- TransitionVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
+ TransitionVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
}
void Visit(xml::Element* node) override {
@@ -185,8 +209,9 @@
class ManifestVisitor : public BaseVisitor {
public:
- ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only)
- : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {}
+ ManifestVisitor(const ResourceFile& file, KeepSet* keep_set, bool main_dex_only)
+ : BaseVisitor(file, keep_set), main_dex_only_(main_dex_only) {
+ }
void Visit(xml::Element* node) override {
if (node->namespace_uri.empty()) {
@@ -241,6 +266,10 @@
BaseVisitor::Visit(node);
}
+ virtual void AddClass(size_t line_number, const std::string& class_name) override {
+ keep_set_->AddManifestClass({file_.name, file_.source.WithLine(line_number)}, class_name);
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(ManifestVisitor);
@@ -249,9 +278,8 @@
std::string default_process_;
};
-bool CollectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keep_set,
- bool main_dex_only) {
- ManifestVisitor visitor(source, keep_set, main_dex_only);
+bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only) {
+ ManifestVisitor visitor(res->file, keep_set, main_dex_only);
if (res->root) {
res->root->Accept(&visitor);
return true;
@@ -259,58 +287,150 @@
return false;
}
-bool CollectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keep_set) {
+bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set) {
if (!res->root) {
return false;
}
switch (res->file.name.type) {
case ResourceType::kLayout: {
- LayoutVisitor visitor(source, keep_set);
+ LayoutVisitor visitor(res->file, keep_set);
res->root->Accept(&visitor);
break;
}
case ResourceType::kXml: {
- XmlResourceVisitor visitor(source, keep_set);
+ XmlResourceVisitor visitor(res->file, keep_set);
res->root->Accept(&visitor);
break;
}
case ResourceType::kTransition: {
- TransitionVisitor visitor(source, keep_set);
+ TransitionVisitor visitor(res->file, keep_set);
res->root->Accept(&visitor);
break;
}
case ResourceType::kMenu: {
- MenuVisitor visitor(source, keep_set);
+ MenuVisitor visitor(res->file, keep_set);
res->root->Accept(&visitor);
break;
}
- default:
+ default: {
+ BaseVisitor visitor(res->file, keep_set);
+ res->root->Accept(&visitor);
break;
+ }
}
return true;
}
bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) {
- for (const auto& entry : keep_set.keep_set_) {
- for (const Source& source : entry.second) {
- *out << "# Referenced at " << source << "\n";
+ for (const auto& entry : keep_set.manifest_class_set_) {
+ for (const UsageLocation& location : entry.second) {
+ *out << "# Referenced at " << location.source << "\n";
}
*out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
}
- for (const auto& entry : keep_set.keep_method_set_) {
- for (const Source& source : entry.second) {
- *out << "# Referenced at " << source << "\n";
+ for (const auto& entry : keep_set.conditional_class_set_) {
+ std::set<UsageLocation> locations;
+ bool can_be_conditional = true;
+ for (const UsageLocation& location : entry.second) {
+ can_be_conditional &= CollectLocations(location, keep_set, &locations);
+ }
+
+ for (const UsageLocation& location : entry.second) {
+ *out << "# Referenced at " << location.source << "\n";
+ }
+ if (keep_set.conditional_keep_rules_ && can_be_conditional) {
+ *out << "-keep class " << entry.first << " {\n ifused class **.R$layout {\n";
+ for (const UsageLocation& location : locations) {
+ auto transformed_name = JavaClassGenerator::TransformToFieldName(location.name.entry);
+ *out << " int " << transformed_name << ";\n";
+ }
+ *out << " };\n <init>(...);\n}\n" << std::endl;
+ } else {
+ *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
+ }
+ }
+
+ for (const auto& entry : keep_set.method_set_) {
+ for (const UsageLocation& location : entry.second) {
+ *out << "# Referenced at " << location.source << "\n";
}
*out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
}
return true;
}
+bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
+ std::set<UsageLocation>* locations) {
+ locations->insert(location);
+
+ // TODO: allow for more reference types if we can determine its safe.
+ if (location.name.type != ResourceType::kLayout) {
+ return false;
+ }
+
+ for (const auto& entry : keep_set.reference_set_) {
+ if (entry.first == location.name) {
+ for (auto& refLocation : entry.second) {
+ // Don't get stuck in loops
+ if (locations->find(refLocation) != locations->end()) {
+ return false;
+ }
+ if (!CollectLocations(refLocation, keep_set, locations)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+class ReferenceVisitor : public ValueVisitor {
+ public:
+ using ValueVisitor::Visit;
+
+ ReferenceVisitor(aapt::IAaptContext* context, ResourceName from, KeepSet* keep_set)
+ : context_(context), from_(from), keep_set_(keep_set) {
+ }
+
+ void Visit(Reference* reference) override {
+ if (reference->name) {
+ ResourceName reference_name = reference->name.value();
+ if (reference_name.package.empty()) {
+ reference_name = ResourceName(context_->GetCompilationPackage(), reference_name.type,
+ reference_name.entry);
+ }
+ keep_set_->AddReference({from_, reference->GetSource()}, reference_name);
+ }
+ }
+
+ private:
+ aapt::IAaptContext* context_;
+ ResourceName from_;
+ KeepSet* keep_set_;
+};
+
+bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table,
+ KeepSet* keep_set) {
+ for (auto& pkg : table->packages) {
+ for (auto& type : pkg->types) {
+ for (auto& entry : type->entries) {
+ for (auto& config_value : entry->values) {
+ ResourceName from(pkg->name, type->type, entry->name);
+ ReferenceVisitor visitor(context, from, keep_set);
+ config_value->value->Accept(&visitor);
+ }
+ }
+ }
+ }
+ return true;
+}
+
} // namespace proguard
} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index 3c349ba..8dbe3c2 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -23,37 +23,80 @@
#include <string>
#include "Resource.h"
+#include "ResourceTable.h"
#include "Source.h"
+#include "ValueVisitor.h"
+#include "androidfw/StringPiece.h"
+#include "process/IResourceTableConsumer.h"
#include "xml/XmlDom.h"
namespace aapt {
namespace proguard {
+struct UsageLocation {
+ ResourceName name;
+ Source source;
+};
+
class KeepSet {
public:
- inline void AddClass(const Source& source, const std::string& class_name) {
- keep_set_[class_name].insert(source);
+ KeepSet() = default;
+
+ KeepSet(bool conditional_keep_rules) : conditional_keep_rules_(conditional_keep_rules) {
}
- inline void AddMethod(const Source& source, const std::string& method_name) {
- keep_method_set_[method_name].insert(source);
+ inline void AddManifestClass(const UsageLocation& file, const std::string& class_name) {
+ manifest_class_set_[class_name].insert(file);
+ }
+
+ inline void AddConditionalClass(const UsageLocation& file, const std::string& class_name) {
+ conditional_class_set_[class_name].insert(file);
+ }
+
+ inline void AddMethod(const UsageLocation& file, const std::string& method_name) {
+ method_set_[method_name].insert(file);
+ }
+
+ inline void AddReference(const UsageLocation& file, const ResourceName& resource_name) {
+ reference_set_[resource_name].insert(file);
}
private:
friend bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);
- std::map<std::string, std::set<Source>> keep_set_;
- std::map<std::string, std::set<Source>> keep_method_set_;
+ friend bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
+ std::set<UsageLocation>* locations);
+
+ bool conditional_keep_rules_ = false;
+ std::map<std::string, std::set<UsageLocation>> manifest_class_set_;
+ std::map<std::string, std::set<UsageLocation>> method_set_;
+ std::map<std::string, std::set<UsageLocation>> conditional_class_set_;
+ std::map<ResourceName, std::set<UsageLocation>> reference_set_;
};
-bool CollectProguardRulesForManifest(const Source& source,
- xml::XmlResource* res, KeepSet* keep_set,
+bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set,
bool main_dex_only = false);
-bool CollectProguardRules(const Source& source, xml::XmlResource* res,
- KeepSet* keep_set);
+bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set);
+bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table,
+ KeepSet* keep_set);
bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);
+bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
+ std::set<UsageLocation>* locations);
+
+//
+// UsageLocation implementation.
+//
+
+inline bool operator==(const UsageLocation& lhs, const UsageLocation& rhs) {
+ return lhs.name == rhs.name;
+}
+
+inline int operator<(const UsageLocation& lhs, const UsageLocation& rhs) {
+ return lhs.name.compare(rhs.name);
+}
+
} // namespace proguard
} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
index 900b073..df3ac8b 100644
--- a/tools/aapt2/java/ProguardRules_test.cpp
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -15,6 +15,7 @@
*/
#include "java/ProguardRules.h"
+#include "link/Linkers.h"
#include "test/Test.h"
@@ -31,7 +32,7 @@
layout->file.name = test::ParseNameOrDie("layout/foo");
proguard::KeepSet set;
- ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
std::stringstream out;
ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
@@ -47,7 +48,7 @@
layout->file.name = test::ParseNameOrDie("layout/foo");
proguard::KeepSet set;
- ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
std::stringstream out;
ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
@@ -65,7 +66,7 @@
layout->file.name = test::ParseNameOrDie("layout/foo");
proguard::KeepSet set;
- ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
std::stringstream out;
ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
@@ -75,6 +76,107 @@
EXPECT_THAT(actual, HasSubstr("com.foo.Baz"));
}
+TEST(ProguardRulesTest, CustomViewRulesAreEmitted) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
+ <View xmlns:android="http://schemas.android.com/apk/res/android">
+ <com.foo.Bar />
+ </View>)");
+ layout->file.name = test::ParseNameOrDie("layout/foo");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+
+ std::stringstream out;
+ ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+ std::string actual = out.str();
+ EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+}
+
+TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) {
+ std::unique_ptr<xml::XmlResource> bar_layout = test::BuildXmlDom(R"(
+ <View xmlns:android="http://schemas.android.com/apk/res/android">
+ <com.foo.Bar />
+ </View>)");
+ bar_layout->file.name = test::ParseNameOrDie("com.foo:layout/bar");
+
+ ResourceTable table;
+ StdErrDiagnostics errDiagnostics;
+ table.AddResource(bar_layout->file.name, ConfigDescription::DefaultConfig(), "",
+ util::make_unique<FileReference>(), &errDiagnostics);
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.foo")
+ .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(&table))
+ .Build();
+
+ std::unique_ptr<xml::XmlResource> foo_layout = test::BuildXmlDom(R"(
+ <View xmlns:android="http://schemas.android.com/apk/res/android">
+ <include layout="@layout/bar" />
+ </View>)");
+ foo_layout->file.name = test::ParseNameOrDie("com.foo:layout/foo");
+
+ XmlReferenceLinker xml_linker;
+ ASSERT_TRUE(xml_linker.Consume(context.get(), bar_layout.get()));
+ ASSERT_TRUE(xml_linker.Consume(context.get(), foo_layout.get()));
+
+ proguard::KeepSet set = proguard::KeepSet(true);
+ ASSERT_TRUE(proguard::CollectProguardRules(bar_layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(foo_layout.get(), &set));
+
+ std::stringstream out;
+ ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+ std::string actual = out.str();
+ EXPECT_THAT(actual, HasSubstr("ifused class **.R$layout"));
+ EXPECT_THAT(actual, HasSubstr("int foo"));
+ EXPECT_THAT(actual, HasSubstr("int bar"));
+ EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+}
+
+TEST(ProguardRulesTest, AliasedLayoutRulesAreConditional) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
+ <View xmlns:android="http://schemas.android.com/apk/res/android">
+ <com.foo.Bar />
+ </View>)");
+ layout->file.name = test::ParseNameOrDie("layout/foo");
+
+ proguard::KeepSet set = proguard::KeepSet(true);
+ set.AddReference({test::ParseNameOrDie("layout/bar"), {}}, layout->file.name);
+ ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+
+ std::stringstream out;
+ ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+ std::string actual = out.str();
+ EXPECT_THAT(actual, HasSubstr("ifused class **.R$layout"));
+ EXPECT_THAT(actual, HasSubstr("int foo"));
+ EXPECT_THAT(actual, HasSubstr("int bar"));
+ EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+}
+
+TEST(ProguardRulesTest, NonLayoutReferencesAreUnconditional) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
+ <View xmlns:android="http://schemas.android.com/apk/res/android">
+ <com.foo.Bar />
+ </View>)");
+ layout->file.name = test::ParseNameOrDie("layout/foo");
+
+ proguard::KeepSet set = proguard::KeepSet(true);
+ set.AddReference({test::ParseNameOrDie("style/MyStyle"), {}}, layout->file.name);
+ ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+
+ std::stringstream out;
+ ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+ std::string actual = out.str();
+ EXPECT_THAT(actual, Not(HasSubstr("ifused")));
+}
+
TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
@@ -83,7 +185,7 @@
layout->file.name = test::ParseNameOrDie("layout/foo");
proguard::KeepSet set;
- ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
std::stringstream out;
ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
@@ -104,7 +206,7 @@
menu->file.name = test::ParseNameOrDie("menu/foo");
proguard::KeepSet set;
- ASSERT_TRUE(proguard::CollectProguardRules({}, menu.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(menu.get(), &set));
std::stringstream out;
ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
index 135df40..674bee1 100644
--- a/tools/incident_section_gen/main.cpp
+++ b/tools/incident_section_gen/main.cpp
@@ -18,6 +18,7 @@
#include <frameworks/base/core/proto/android/os/incident.pb.h>
#include <map>
+#include <set>
#include <string>
using namespace android;
@@ -27,6 +28,60 @@
using namespace google::protobuf::internal;
using namespace std;
+/**
+ * Implementation details:
+ * This binary auto generates .cpp files for incident and incidentd.
+ *
+ * When argument "incident" is specified, it generates incident_section.cpp file.
+ *
+ * When argument "incidentd" is specified, it generates section_list.cpp file.
+ *
+ * In section_list.cpp file, it generates a SECTION_LIST array and a PRIVACY_POLICY_LIST array.
+ * For SECTION_LIST, it generates Section.h classes only for proto fields with section option enabled.
+ * For PRIVACY_POLICY_LIST, it generates Privacy.h classes only for proto fields with privacy option enabled.
+ *
+ * For Privacy struct, it is possible to have self recursion definitions since protobuf is defining "classes"
+ * So the logic to handle it becomes very complicated when Privacy tag of a message contains a list of Privacies
+ * of its sub-messages. The code also handles multiple depth of self recursion fields.
+ *
+ * For example here is a one level self recursion message WindowManager:
+ * message WindowState {
+ * string state = 1 [(privacy).dest = LOCAL];
+ * int32 display_id = 2;
+ * repeated WindowState child_windows = 3;
+ * }
+ *
+ * message WindowManager {
+ * WindowState my_window = 1;
+ * }
+ *
+ * When generating Privacy options for WindowManager, this tool will generate cpp syntax source code:
+ *
+ * #include "section_list.h"
+ * ...
+ * Privacy WindowState_state { 1, 9, NULL, LOCAL, NULL }; // first two integers are values for field id and proto type.
+ * Privacy WindowState_child_windows { 3, 11, NULL, DEFAULT, NULL }; // reserved for WindowState_LIST
+ * Privacy* WindowState_MSG_[] = {
+ * &WindowState_state,
+ * // display id is default, nothing is generated.
+ * &WindowState_child_windows,
+ * NULL // terminator of the array
+ * };
+ * Privacy WindowState_my_window { 1, 11, WindowState_my_window_LIST, DEFAULT, NULL };
+ *
+ * createList() {
+ * ...
+ * WindowState_child_windows.children = WindowState_my_window_LIST; // point to its own definition after the list is defined.
+ * ...
+ * }
+ *
+ * const Privacy** PRIVACY_POLICY_LIST = createList();
+ * const int PRIVACY_POLICY_COUNT = 1;
+ */
+
+// The assignments will be called when constructs PRIVACY_POLICY_LIST, has to be global variable
+vector<string> gSelfRecursionAssignments;
+
static inline void emptyline() {
printf("\n");
}
@@ -38,7 +93,7 @@
emptyline();
}
-// ================================================================================
+// ======================== incident_sections =============================
static bool generateIncidentSectionsCpp(Descriptor const* descriptor)
{
generateHead("incident_sections");
@@ -73,7 +128,7 @@
return true;
}
-// ================================================================================
+// ========================= section_list ===================================
static void splitAndPrint(const string& args) {
size_t base = 0;
size_t found;
@@ -88,12 +143,12 @@
}
}
-static const std::string replaceAll(const string& field_name, const char oldC, const string& newS) {
- if (field_name.find_first_of(oldC) == field_name.npos) return field_name.c_str();
+static string replaceAll(const string& fieldName, const char oldC, const string& newS) {
+ if (fieldName.find_first_of(oldC) == fieldName.npos) return fieldName.c_str();
size_t pos = 0, idx = 0;
- char* res = new char[field_name.size() * newS.size() + 1]; // assign a larger buffer
- while (pos != field_name.size()) {
- char cur = field_name[pos++];
+ char* res = new char[fieldName.size() * newS.size() + 1]; // assign a larger buffer
+ while (pos != fieldName.size()) {
+ char cur = fieldName[pos++];
if (cur != oldC) {
res[idx++] = cur;
continue;
@@ -104,92 +159,162 @@
}
}
res[idx] = '\0';
- std::string result(res);
+ string result(res);
delete [] res;
return result;
}
-static inline bool isDefaultDest(const FieldDescriptor* field) {
- return field->options().GetExtension(privacy).dest() == PrivacyFlags::default_instance().dest();
+static string getFieldName(const FieldDescriptor* field) {
+ return replaceAll(field->full_name(), '.', "__");
}
+static string getMessageTypeName(const Descriptor* descriptor) {
+ return replaceAll(descriptor->full_name(), '.', "_") + "_MSG_";
+}
+
+static inline SectionFlags getSectionFlags(const FieldDescriptor* field) {
+ return field->options().GetExtension(section);
+}
+
+static inline PrivacyFlags getPrivacyFlags(const FieldDescriptor* field) {
+ return field->options().GetExtension(privacy);
+}
+
+static inline bool isDefaultField(const FieldDescriptor* field) {
+ return getPrivacyFlags(field).dest() == PrivacyFlags::default_instance().dest();
+}
+
+static bool isDefaultMessageImpl(const Descriptor* descriptor, set<string>* parents) {
+ int N = descriptor->field_count();
+ parents->insert(descriptor->full_name());
+ for (int i=0; i<N; ++i) {
+ const FieldDescriptor* field = descriptor->field(i);
+ // look at if the current field is default or not, return false immediately
+ if (!isDefaultField(field)) return false;
+
+ switch (field->type()) {
+ case FieldDescriptor::TYPE_MESSAGE:
+ // if self recursion, don't go deep.
+ if (parents->find(field->message_type()->full_name()) != parents->end()) break;
+ // if is a default message, just continue
+ if (isDefaultMessageImpl(field->message_type(), parents)) break;
+ // sub message is not default, so this message is always not default
+ return false;
+ case FieldDescriptor::TYPE_STRING:
+ if (getPrivacyFlags(field).patterns_size() != 0) return false;
+ default:
+ continue;
+ }
+ }
+ parents->erase(descriptor->full_name());
+ return true;
+}
+
+static bool isDefaultMessage(const Descriptor* descriptor) {
+ set<string> parents;
+ return isDefaultMessageImpl(descriptor, &parents);
+}
+
+// This function is called for looking at privacy tags for a message type and recursively its sub-messages
+// It prints out each fields's privacy tags and a List of Privacy of the message itself (don't print default values)
// Returns false if the descriptor doesn't have any non default privacy flags set, including its submessages
-static bool generatePrivacyFlags(const Descriptor* descriptor, const char* alias, map<string, bool> &msgNames) {
+static bool generatePrivacyFlags(const Descriptor* descriptor, map<string, bool> &msgNames, set<string>* parents) {
bool hasDefaultFlags[descriptor->field_count()];
+
+ string messageTypeName = getMessageTypeName(descriptor);
+ // if the message is already defined, skip it.
+ if (msgNames.find(messageTypeName) != msgNames.end()) {
+ bool hasDefault = msgNames[messageTypeName];
+ return !hasDefault; // don't generate if it has default privacy.
+ }
+ // insert the message type name so sub-message will figure out if self-recursion occurs
+ parents->insert(messageTypeName);
+
// iterate though its field and generate sub flags first
for (int i=0; i<descriptor->field_count(); i++) {
hasDefaultFlags[i] = true; // set default to true
+
const FieldDescriptor* field = descriptor->field(i);
- const std::string field_name_str = replaceAll(field->full_name(), '.', "__");
- const char* field_name = field_name_str.c_str();
- // check if the same name is already defined
- if (msgNames.find(field_name) != msgNames.end()) {
- hasDefaultFlags[i] = msgNames[field_name];
+ const string fieldName = getFieldName(field);
+ // check if the same field name is already defined.
+ if (msgNames.find(fieldName) != msgNames.end()) {
+ hasDefaultFlags[i] = msgNames[fieldName];
continue;
};
- PrivacyFlags p = field->options().GetExtension(privacy);
+ PrivacyFlags p = getPrivacyFlags(field);
+ string fieldMessageName;
switch (field->type()) {
case FieldDescriptor::TYPE_MESSAGE:
- if (generatePrivacyFlags(field->message_type(), field_name, msgNames)) {
- printf("Privacy %s { %d, %d, %s_LIST, %d, NULL };\n", field_name, field->number(),
- field->type(), field_name, p.dest());
- } else if (isDefaultDest(field)) {
+ fieldMessageName = getMessageTypeName(field->message_type());
+ if (parents->find(fieldMessageName) != parents->end()) { // Self-Recursion proto definition
+ if (isDefaultField(field)) {
+ hasDefaultFlags[i] = isDefaultMessage(field->message_type());
+ } else {
+ hasDefaultFlags[i] = false;
+ }
+ if (!hasDefaultFlags[i]) {
+ printf("Privacy %s = { %d, %d, NULL, %d, NULL }; // self recursion field of %s\n",
+ fieldName.c_str(), field->number(), field->type(), p.dest(), fieldMessageName.c_str());
+ // generate the assignment and used to construct createList function later on.
+ gSelfRecursionAssignments.push_back(fieldName + ".children = " + fieldMessageName);
+ }
+ break;
+ } else if (generatePrivacyFlags(field->message_type(), msgNames, parents)) {
+ printf("Privacy %s = { %d, %d, %s, %d, NULL };\n", fieldName.c_str(), field->number(),
+ field->type(), fieldMessageName.c_str(), p.dest());
+ } else if (isDefaultField(field)) {
// don't create a new privacy if the value is default.
break;
- } else{
- printf("Privacy %s { %d, %d, NULL, %d, NULL };\n", field_name, field->number(),
+ } else {
+ printf("Privacy %s = { %d, %d, NULL, %d, NULL };\n", fieldName.c_str(), field->number(),
field->type(), p.dest());
}
hasDefaultFlags[i] = false;
break;
case FieldDescriptor::TYPE_STRING:
- if (isDefaultDest(field) && p.patterns_size() == 0) break;
+ if (isDefaultField(field) && p.patterns_size() == 0) break;
- printf("const char* %s_patterns[] = {\n", field_name);
+ printf("const char* %s_patterns[] = {\n", fieldName.c_str());
for (int i=0; i<p.patterns_size(); i++) {
// the generated string need to escape backslash as well, need to dup it here
printf(" \"%s\",\n", replaceAll(p.patterns(i), '\\', "\\\\").c_str());
}
printf(" NULL };\n");
- printf("Privacy %s { %d, %d, NULL, %d, %s_patterns };\n", field_name, field->number(),
- field->type(), p.dest(), field_name);
+ printf("Privacy %s = { %d, %d, NULL, %d, %s_patterns };\n", fieldName.c_str(), field->number(),
+ field->type(), p.dest(), fieldName.c_str());
hasDefaultFlags[i] = false;
break;
default:
- if (isDefaultDest(field)) break;
- printf("Privacy %s { %d, %d, NULL, %d, NULL };\n", field_name, field->number(),
+ if (isDefaultField(field)) break;
+ printf("Privacy %s = { %d, %d, NULL, %d, NULL };\n", fieldName.c_str(), field->number(),
field->type(), p.dest());
hasDefaultFlags[i] = false;
}
// add the field name to message map, true means it has default flags
- msgNames[field_name] = hasDefaultFlags[i];
+ msgNames[fieldName] = hasDefaultFlags[i];
}
bool allDefaults = true;
for (int i=0; i<descriptor->field_count(); i++) {
allDefaults &= hasDefaultFlags[i];
}
+
+ parents->erase(messageTypeName); // erase the message type name when exit the message.
+ msgNames[messageTypeName] = allDefaults; // store the privacy tags of the message here to avoid overhead.
+
if (allDefaults) return false;
emptyline();
-
- bool needConst = strcmp(alias, "PRIVACY_POLICY") == 0;
int policyCount = 0;
-
- printf("%sPrivacy* %s_LIST[] = {\n", needConst ? "const " : "", alias);
+ printf("Privacy* %s[] = {\n", messageTypeName.c_str());
for (int i=0; i<descriptor->field_count(); i++) {
const FieldDescriptor* field = descriptor->field(i);
if (hasDefaultFlags[i]) continue;
- printf(" &%s,\n", replaceAll(field->full_name(), '.', "__").c_str());
+ printf(" &%s,\n", getFieldName(field).c_str());
policyCount++;
}
- if (needConst) {
- printf("};\n\n");
- printf("const int PRIVACY_POLICY_COUNT = %d;\n", policyCount);
- } else {
- printf(" NULL };\n");
- }
+ printf(" NULL };\n");
emptyline();
return true;
}
@@ -198,6 +323,8 @@
generateHead("section_list");
// generates SECTION_LIST
+ printf("// Generate SECTION_LIST.\n\n");
+
printf("const Section* SECTION_LIST[] = {\n");
for (int i=0; i<descriptor->field_count(); i++) {
const FieldDescriptor* field = descriptor->field(i);
@@ -205,7 +332,7 @@
if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
continue;
}
- const SectionFlags s = field->options().GetExtension(section);
+ const SectionFlags s = getSectionFlags(field);
switch (s.type()) {
case SECTION_NONE:
continue;
@@ -225,16 +352,73 @@
}
}
printf(" NULL };\n");
+
+ emptyline();
+ printf("// =============================================================================\n");
emptyline();
- // generates PRIVACY_POLICY
+ // generates PRIVACY_POLICY_LIST
+ printf("// Generate PRIVACY_POLICY_LIST.\n\n");
map<string, bool> messageNames;
- if (!generatePrivacyFlags(descriptor, "PRIVACY_POLICY", messageNames)) {
- // if no privacy options set at all, define an empty list
- printf("const Privacy* PRIVACY_POLICY_LIST[] = {};\n");
- printf("const int PRIVACY_POLICY_COUNT = 0;\n");
+ set<string> parents;
+ bool skip[descriptor->field_count()];
+
+ for (int i=0; i<descriptor->field_count(); i++) {
+ const FieldDescriptor* field = descriptor->field(i);
+ const string fieldName = getFieldName(field);
+ PrivacyFlags p = getPrivacyFlags(field);
+
+ skip[i] = true;
+
+ if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
+ continue;
+ }
+ // generate privacy flags for each field.
+ if (generatePrivacyFlags(field->message_type(), messageNames, &parents)) {
+ printf("Privacy %s { %d, %d, %s, %d, NULL };\n", fieldName.c_str(), field->number(),
+ field->type(), getMessageTypeName(field->message_type()).c_str(), p.dest());
+ } else if (isDefaultField(field)) {
+ continue; // don't create a new privacy if the value is default.
+ } else {
+ printf("Privacy %s { %d, %d, NULL, %d, NULL };\n", fieldName.c_str(), field->number(),
+ field->type(), p.dest());
+ }
+ skip[i] = false;
}
+ // generate final PRIVACY_POLICY_LIST
+ emptyline();
+ int policyCount = 0;
+ if (gSelfRecursionAssignments.empty()) {
+ printf("Privacy* privacyArray[] = {\n");
+ for (int i=0; i<descriptor->field_count(); i++) {
+ if (skip[i]) continue;
+ printf(" &%s,\n", getFieldName(descriptor->field(i)).c_str());
+ policyCount++;
+ }
+ printf("};\n\n");
+ printf("const Privacy** PRIVACY_POLICY_LIST = const_cast<const Privacy**>(privacyArray);\n\n");
+ printf("const int PRIVACY_POLICY_COUNT = %d;\n", policyCount);
+ } else {
+ for (int i=0; i<descriptor->field_count(); i++) {
+ if (!skip[i]) policyCount++;
+ }
+
+ printf("static const Privacy** createList() {\n");
+ for (size_t i=0; i<gSelfRecursionAssignments.size(); ++i) {
+ printf(" %s;\n", gSelfRecursionAssignments[i].c_str());
+ }
+ printf(" Privacy** privacyArray = (Privacy**)malloc(%d * sizeof(Privacy**));\n", policyCount);
+ policyCount = 0; // reset
+ for (int i=0; i<descriptor->field_count(); i++) {
+ if (skip[i]) continue;
+ printf(" privacyArray[%d] = &%s;\n", policyCount++, getFieldName(descriptor->field(i)).c_str());
+ }
+ printf(" return const_cast<const Privacy**>(privacyArray);\n");
+ printf("}\n\n");
+ printf("const Privacy** PRIVACY_POLICY_LIST = createList();\n\n");
+ printf("const int PRIVACY_POLICY_COUNT = %d;\n", policyCount);
+ }
return true;
}
diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp
new file mode 100644
index 0000000..6dd6059
--- /dev/null
+++ b/tools/locked_region_code_injection/Android.bp
@@ -0,0 +1,12 @@
+java_library_host {
+ name: "lockedregioncodeinjection",
+ manifest: "manifest.txt",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "asm-6.0",
+ "asm-commons-6.0",
+ "asm-tree-6.0",
+ "asm-analysis-6.0",
+ "guava-21.0",
+ ],
+}
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
deleted file mode 100644
index 3f65151..0000000
--- a/tools/locked_region_code_injection/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_JAR_MANIFEST := manifest.txt
-LOCAL_MODULE := lockedregioncodeinjection
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_JAVA_LIBRARIES := \
- asm-6.0 \
- asm-commons-6.0 \
- asm-tree-6.0 \
- asm-analysis-6.0 \
- guava-21.0 \
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
index a60f2a2..ee0e36c 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
@@ -76,7 +76,7 @@
private MethodVisitor chain;
public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) {
- super(Opcodes.ASM6, mn);
+ super(Utils.ASM_VERSION, mn);
assert owner != null;
this.owner = owner;
this.chain = chain;
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
index d2a2e7b..219c2b3 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
@@ -19,7 +19,7 @@
public class Utils {
- public static final int ASM_VERSION = Opcodes.ASM5;
+ public static final int ASM_VERSION = Opcodes.ASM6;
/**
* Reads a comma separated configuration similar to the Jack definition.
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 5d29268..f76196d 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -22,6 +22,7 @@
namespace android {
namespace stats_log_api_gen {
+using google::protobuf::EnumDescriptor;
using google::protobuf::FieldDescriptor;
using google::protobuf::FileDescriptor;
using google::protobuf::SourceLocation;
@@ -120,7 +121,7 @@
case FieldDescriptor::TYPE_UINT32:
return JAVA_TYPE_INT;
case FieldDescriptor::TYPE_ENUM:
- return JAVA_TYPE_INT;
+ return JAVA_TYPE_ENUM;
case FieldDescriptor::TYPE_SFIXED32:
return JAVA_TYPE_INT;
case FieldDescriptor::TYPE_SFIXED64:
@@ -208,7 +209,6 @@
errorCount++;
continue;
}
-
}
// Check that if there's a WorkSource, it's at position 1.
@@ -228,15 +228,26 @@
AtomDecl atomDecl(atomField->number(), atomField->name(), atom->name());
- // Build the type signature
+ // Build the type signature and the atom data.
vector<java_type_t> signature;
for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin();
it != fields.end(); it++) {
const FieldDescriptor* field = it->second;
java_type_t javaType = java_type(field);
- atomDecl.fields.push_back(AtomField(field->name(), javaType));
- signature.push_back(javaType);
+ AtomField atField(field->name(), javaType);
+ if (javaType == JAVA_TYPE_ENUM) {
+ // All enums are treated as ints when it comes to function signatures.
+ signature.push_back(JAVA_TYPE_INT);
+ const EnumDescriptor* enumDescriptor = field->enum_type();
+ for (int i = 0; i < enumDescriptor->value_count(); i++) {
+ atField.enumValues[enumDescriptor->value(i)->number()] =
+ enumDescriptor->value(i)->name().c_str();
+ }
+ } else {
+ signature.push_back(javaType);
+ }
+ atomDecl.fields.push_back(atField);
}
atoms->signatures.insert(signature);
@@ -261,5 +272,3 @@
} // namespace stats_log_api_gen
} // namespace android
-
-
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 50af7ea..2f840d7 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -22,10 +22,12 @@
#include <set>
#include <vector>
+#include <map>
namespace android {
namespace stats_log_api_gen {
+using std::map;
using std::set;
using std::string;
using std::vector;
@@ -44,6 +46,7 @@
JAVA_TYPE_FLOAT = 5,
JAVA_TYPE_DOUBLE = 6,
JAVA_TYPE_STRING = 7,
+ JAVA_TYPE_ENUM = 8,
JAVA_TYPE_OBJECT = -1,
JAVA_TYPE_BYTE_ARRAY = -2,
@@ -57,8 +60,13 @@
string name;
java_type_t javaType;
+ // If the field is of type enum, the following map contains the list of enum values.
+ map<int /* numeric value */, string /* value name */> enumValues;
+
inline AtomField() :name(), javaType(JAVA_TYPE_UNKNOWN) {}
- inline AtomField(const AtomField& that) :name(that.name), javaType(that.javaType) {}
+ inline AtomField(const AtomField& that) :name(that.name),
+ javaType(that.javaType),
+ enumValues(that.enumValues) {}
inline AtomField(string n, java_type_t jt) :name(n), javaType(jt) {}
inline ~AtomField() {}
};
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index aaea4f6..6350b72 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -56,6 +56,7 @@
case JAVA_TYPE_BOOLEAN:
return "bool";
case JAVA_TYPE_INT:
+ case JAVA_TYPE_ENUM:
return "int32_t";
case JAVA_TYPE_LONG:
return "int64_t";
@@ -77,6 +78,7 @@
case JAVA_TYPE_BOOLEAN:
return "boolean";
case JAVA_TYPE_INT:
+ case JAVA_TYPE_ENUM:
return "int";
case JAVA_TYPE_LONG:
return "long";
@@ -173,7 +175,7 @@
fprintf(out, " */\n");
fprintf(out, "\n");
fprintf(out, "/**\n");
- fprintf(out, " * Constants for event codes.\n");
+ fprintf(out, " * Constants for atom codes.\n");
fprintf(out, " */\n");
fprintf(out, "enum {\n");
@@ -240,9 +242,9 @@
fprintf(out, " * @hide\n");
fprintf(out, " */\n");
fprintf(out, "public final class StatsLog {\n");
- fprintf(out, " // Constants for event codes.\n");
+ fprintf(out, " // Constants for atom codes.\n");
- // Print constants
+ // Print constants for the atom codes.
for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
atom != atoms.decls.end(); atom++) {
string constant = make_constant_name(atom->name);
@@ -260,6 +262,27 @@
}
fprintf(out, "\n");
+ // Print constants for the enum values.
+ fprintf(out, " // Constants for enum values.\n\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ for (vector<AtomField>::const_iterator field = atom->fields.begin();
+ field != atom->fields.end(); field++) {
+ if (field->javaType == JAVA_TYPE_ENUM) {
+ fprintf(out, " // Values for %s.%s\n", atom->message.c_str(), field->name.c_str());
+ for (map<int, string>::const_iterator value = field->enumValues.begin();
+ value != field->enumValues.end(); value++) {
+ fprintf(out, " public static final int %s__%s__%s = %d;\n",
+ make_constant_name(atom->message).c_str(),
+ make_constant_name(field->name).c_str(),
+ make_constant_name(value->second).c_str(),
+ value->first);
+ }
+ fprintf(out, "\n");
+ }
+ }
+ }
+
// Print write methods
fprintf(out, " // Write methods\n");
for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
@@ -286,6 +309,7 @@
case JAVA_TYPE_BOOLEAN:
return "jboolean";
case JAVA_TYPE_INT:
+ case JAVA_TYPE_ENUM:
return "jint";
case JAVA_TYPE_LONG:
return "jlong";
@@ -311,6 +335,7 @@
result += "_boolean";
break;
case JAVA_TYPE_INT:
+ case JAVA_TYPE_ENUM:
result += "_int";
break;
case JAVA_TYPE_LONG:
@@ -340,6 +365,7 @@
case JAVA_TYPE_BOOLEAN:
return "Z";
case JAVA_TYPE_INT:
+ case JAVA_TYPE_ENUM:
return "I";
case JAVA_TYPE_LONG:
return "J";
diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp
index 1bd2e3d..073f2cf 100644
--- a/tools/stats_log_api_gen/test_collation.cpp
+++ b/tools/stats_log_api_gen/test_collation.cpp
@@ -24,6 +24,7 @@
namespace android {
namespace stats_log_api_gen {
+using std::map;
using std::set;
using std::vector;
@@ -54,6 +55,29 @@
EXPECT_TRUE(set_contains_vector(s, count, __VA_ARGS__)); \
} while(0)
+/** Expects that the provided atom has no enum values for any field. */
+#define EXPECT_NO_ENUM_FIELD(atom) \
+ do { \
+ for (vector<AtomField>::const_iterator field = atom->fields.begin(); \
+ field != atom->fields.end(); field++) { \
+ EXPECT_TRUE(field->enumValues.empty()); \
+ } \
+ } while(0)
+
+/** Expects that exactly one specific field has expected enum values. */
+#define EXPECT_HAS_ENUM_FIELD(atom, field_name, values) \
+ do { \
+ for (vector<AtomField>::const_iterator field = atom->fields.begin(); \
+ field != atom->fields.end(); field++) { \
+ if (field->name == field_name) { \
+ EXPECT_EQ(field->enumValues, values); \
+ } else { \
+ EXPECT_TRUE(field->enumValues.empty()); \
+ } \
+ } \
+ } while(0)
+
+
/**
* Test a correct collation, with all the types.
*/
@@ -94,21 +118,28 @@
EXPECT_EQ(1, atom->code);
EXPECT_EQ("int_atom", atom->name);
EXPECT_EQ("IntAtom", atom->message);
+ EXPECT_NO_ENUM_FIELD(atom);
atom++;
EXPECT_EQ(2, atom->code);
EXPECT_EQ("out_of_order_atom", atom->name);
EXPECT_EQ("OutOfOrderAtom", atom->message);
+ EXPECT_NO_ENUM_FIELD(atom);
atom++;
EXPECT_EQ(3, atom->code);
EXPECT_EQ("another_int_atom", atom->name);
EXPECT_EQ("AnotherIntAtom", atom->message);
+ EXPECT_NO_ENUM_FIELD(atom);
atom++;
EXPECT_EQ(4, atom->code);
EXPECT_EQ("all_types_atom", atom->name);
EXPECT_EQ("AllTypesAtom", atom->message);
+ map<int, string> enumValues;
+ enumValues[0] = "VALUE0";
+ enumValues[1] = "VALUE1";
+ EXPECT_HAS_ENUM_FIELD(atom, "enum_field", enumValues);
atom++;
EXPECT_TRUE(atom == atoms.decls.end());
@@ -125,7 +156,7 @@
}
/**
- * Test that atoms that have non-primitve types are rejected.
+ * Test that atoms that have non-primitive types are rejected.
*/
TEST(CollationTest, FailOnBadTypes) {
Atoms atoms;
@@ -165,7 +196,5 @@
EXPECT_EQ(1, errorCount);
}
-
} // namespace stats_log_api_gen
} // namespace android
-
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 756549c..dc5c14e 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -21,10 +21,32 @@
name: "protoc-gen-stream-defaults",
srcs: [
"Errors.cpp",
+ "stream_proto_utils.cpp",
"string_utils.cpp",
],
+
+ shared_libs: ["libprotoc"],
}
+cc_library {
+ name: "streamingflags",
+ host_supported: true,
+ proto: {
+ export_proto_headers: true,
+ include_dirs: ["external/protobuf/src"],
+ },
+
+ target: {
+ host: {
+ proto: {
+ type: "full",
+ },
+ srcs: [
+ "stream.proto",
+ ],
+ },
+ },
+}
cc_binary_host {
name: "protoc-gen-javastream",
@@ -33,7 +55,6 @@
],
defaults: ["protoc-gen-stream-defaults"],
- shared_libs: ["libprotoc"],
}
cc_binary_host {
@@ -43,5 +64,5 @@
],
defaults: ["protoc-gen-stream-defaults"],
- shared_libs: ["libprotoc"],
+ static_libs: ["streamingflags"],
}
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index d4e1b7a..4816984 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -1,106 +1,23 @@
#include "Errors.h"
+#include "stream_proto_utils.h"
#include "string_utils.h"
-#include "google/protobuf/compiler/plugin.pb.h"
-#include "google/protobuf/io/zero_copy_stream_impl.h"
-#include "google/protobuf/text_format.h"
+#include <frameworks/base/tools/streaming_proto/stream.pb.h>
#include <iomanip>
#include <iostream>
#include <sstream>
using namespace android::stream_proto;
-using namespace google::protobuf;
-using namespace google::protobuf::compiler;
using namespace google::protobuf::io;
using namespace std;
-/**
- * Position of the field type in a (long long) fieldId.
- */
-const uint64_t FIELD_TYPE_SHIFT = 32;
-
-//
-// FieldId flags for whether the field is single, repeated or packed.
-// TODO: packed is not supported yet.
-//
-const uint64_t FIELD_COUNT_SHIFT = 40;
-const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_UNKNOWN = 0;
-const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_PACKED = 4ULL << FIELD_COUNT_SHIFT;
-
-// Indent
-const string INDENT = " ";
-
-/**
- * See if this is the file for this request, and not one of the imported ones.
- */
-static bool
-should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
-{
- const int N = request.file_to_generate_size();
- for (int i=0; i<N; i++) {
- if (request.file_to_generate(i) == file) {
- return true;
- }
- }
- return false;
-}
-
static string
make_filename(const FileDescriptorProto& file_descriptor)
{
return file_descriptor.name() + ".h";
}
-static string
-get_proto_type(const FieldDescriptorProto& field)
-{
- switch (field.type()) {
- case FieldDescriptorProto::TYPE_DOUBLE:
- return "double";
- case FieldDescriptorProto::TYPE_FLOAT:
- return "float";
- case FieldDescriptorProto::TYPE_INT64:
- return "int64";
- case FieldDescriptorProto::TYPE_UINT64:
- return "uint64";
- case FieldDescriptorProto::TYPE_INT32:
- return "int32";
- case FieldDescriptorProto::TYPE_FIXED64:
- return "fixed64";
- case FieldDescriptorProto::TYPE_FIXED32:
- return "fixed32";
- case FieldDescriptorProto::TYPE_BOOL:
- return "bool";
- case FieldDescriptorProto::TYPE_STRING:
- return "string";
- case FieldDescriptorProto::TYPE_GROUP:
- return "group<unsupported!>";
- case FieldDescriptorProto::TYPE_MESSAGE:
- return field.type_name();
- case FieldDescriptorProto::TYPE_BYTES:
- return "bytes";
- case FieldDescriptorProto::TYPE_UINT32:
- return "uint32";
- case FieldDescriptorProto::TYPE_ENUM:
- return field.type_name();
- case FieldDescriptorProto::TYPE_SFIXED32:
- return "sfixed32";
- case FieldDescriptorProto::TYPE_SFIXED64:
- return "sfixed64";
- case FieldDescriptorProto::TYPE_SINT32:
- return "sint32";
- case FieldDescriptorProto::TYPE_SINT64:
- return "sint64";
- default:
- // won't happen
- return "void";
- }
-}
-
static void
write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
{
@@ -115,27 +32,6 @@
text << endl;
}
-static uint64_t
-get_field_id(const FieldDescriptorProto& field)
-{
- // Number
- uint64_t result = (uint64_t)field.number();
-
- // Type
- result |= (uint64_t)field.type() << FIELD_TYPE_SHIFT;
-
- // Count
- if (field.options().packed()) {
- result |= FIELD_COUNT_PACKED;
- } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
- result |= FIELD_COUNT_REPEATED;
- } else {
- result |= FIELD_COUNT_SINGLE;
- }
-
- return result;
-}
-
static void
write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
{
@@ -160,6 +56,12 @@
text << endl;
}
+static inline bool
+should_generate_fields_mapping(const DescriptorProto& message)
+{
+ return message.options().GetExtension(stream).enable_fields_mapping();
+}
+
static void
write_message(stringstream& text, const DescriptorProto& message, const string& indent)
{
@@ -167,8 +69,7 @@
const string indented = indent + INDENT;
text << indent << "// message " << message.name() << endl;
- text << indent << "class " << message.name() << " {" << endl;
- text << indent << "public:" << endl;
+ text << indent << "namespace " << message.name() << " {" << endl;
// Enums
N = message.enum_type_size();
@@ -188,12 +89,27 @@
write_field(text, message.field(i), indented);
}
- text << indent << "};" << endl;
+ if (should_generate_fields_mapping(message)) {
+ N = message.field_size();
+ text << indented << "const int _FIELD_COUNT = " << N << ";" << endl;
+ text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl;
+ for (int i=0; i<N; i++) {
+ text << indented << INDENT << "\"" << message.field(i).name() << "\"," << endl;
+ }
+ text << indented << "};" << endl;
+ text << indented << "const uint64_t _FIELD_IDS[" << N << "] = {" << endl;
+ for (int i=0; i<N; i++) {
+ text << indented << INDENT << make_constant_name(message.field(i).name()) << "," << endl;
+ }
+ text << indented << "};" << endl << endl;
+ }
+
+ text << indent << "} //" << message.name() << endl;
text << endl;
}
static void
-write_cpp_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+write_header_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
{
stringstream text;
@@ -255,7 +171,7 @@
for (int i=0; i<N; i++) {
const FileDescriptorProto& file_descriptor = request.proto_file(i);
if (should_generate_for_file(request, file_descriptor.name())) {
- write_cpp_file(&response, file_descriptor);
+ write_header_file(&response, file_descriptor);
}
}
@@ -270,4 +186,4 @@
/* code */
return 0;
-}
\ No newline at end of file
+}
diff --git a/tools/streaming_proto/java/main.cpp b/tools/streaming_proto/java/main.cpp
index b7d594b..c9c50a5 100644
--- a/tools/streaming_proto/java/main.cpp
+++ b/tools/streaming_proto/java/main.cpp
@@ -1,11 +1,7 @@
#include "Errors.h"
-
+#include "stream_proto_utils.h"
#include "string_utils.h"
-#include "google/protobuf/compiler/plugin.pb.h"
-#include "google/protobuf/io/zero_copy_stream_impl.h"
-#include "google/protobuf/text_format.h"
-
#include <stdio.h>
#include <iomanip>
#include <iostream>
@@ -13,51 +9,9 @@
#include <map>
using namespace android::stream_proto;
-using namespace google::protobuf;
-using namespace google::protobuf::compiler;
using namespace google::protobuf::io;
using namespace std;
-const int FIELD_TYPE_SHIFT = 32;
-const uint64_t FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
-
-const int FIELD_COUNT_SHIFT = 40;
-const uint64_t FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
-
-
-/**
- * See if this is the file for this request, and not one of the imported ones.
- */
-static bool
-should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
-{
- const int N = request.file_to_generate_size();
- for (int i=0; i<N; i++) {
- if (request.file_to_generate(i) == file) {
- return true;
- }
- }
- return false;
-}
-
/**
* If the descriptor gives us a class name, use that. Otherwise make one up from
* the filename of the .proto file.
@@ -112,7 +66,7 @@
static string
indent_more(const string& indent)
{
- return indent + " ";
+ return indent + INDENT;
}
/**
@@ -133,130 +87,6 @@
}
/**
- * Get the string name for a field.
- */
-static string
-get_proto_type(const FieldDescriptorProto& field)
-{
- switch (field.type()) {
- case FieldDescriptorProto::TYPE_DOUBLE:
- return "double";
- case FieldDescriptorProto::TYPE_FLOAT:
- return "float";
- case FieldDescriptorProto::TYPE_INT64:
- return "int64";
- case FieldDescriptorProto::TYPE_UINT64:
- return "uint64";
- case FieldDescriptorProto::TYPE_INT32:
- return "int32";
- case FieldDescriptorProto::TYPE_FIXED64:
- return "fixed64";
- case FieldDescriptorProto::TYPE_FIXED32:
- return "fixed32";
- case FieldDescriptorProto::TYPE_BOOL:
- return "bool";
- case FieldDescriptorProto::TYPE_STRING:
- return "string";
- case FieldDescriptorProto::TYPE_GROUP:
- return "group<unsupported!>";
- case FieldDescriptorProto::TYPE_MESSAGE:
- return field.type_name();
- case FieldDescriptorProto::TYPE_BYTES:
- return "bytes";
- case FieldDescriptorProto::TYPE_UINT32:
- return "uint32";
- case FieldDescriptorProto::TYPE_ENUM:
- return field.type_name();
- case FieldDescriptorProto::TYPE_SFIXED32:
- return "sfixed32";
- case FieldDescriptorProto::TYPE_SFIXED64:
- return "sfixed64";
- case FieldDescriptorProto::TYPE_SINT32:
- return "sint32";
- case FieldDescriptorProto::TYPE_SINT64:
- return "sint64";
- default:
- // won't happen
- return "void";
- }
-}
-
-static uint64_t
-get_field_id(const FieldDescriptorProto& field)
-{
- // Number
- uint64_t result = (uint32_t)field.number();
-
- // Type
- switch (field.type()) {
- case FieldDescriptorProto::TYPE_DOUBLE:
- result |= FIELD_TYPE_DOUBLE;
- break;
- case FieldDescriptorProto::TYPE_FLOAT:
- result |= FIELD_TYPE_FLOAT;
- break;
- case FieldDescriptorProto::TYPE_INT64:
- result |= FIELD_TYPE_INT64;
- break;
- case FieldDescriptorProto::TYPE_UINT64:
- result |= FIELD_TYPE_UINT64;
- break;
- case FieldDescriptorProto::TYPE_INT32:
- result |= FIELD_TYPE_INT32;
- break;
- case FieldDescriptorProto::TYPE_FIXED64:
- result |= FIELD_TYPE_FIXED64;
- break;
- case FieldDescriptorProto::TYPE_FIXED32:
- result |= FIELD_TYPE_FIXED32;
- break;
- case FieldDescriptorProto::TYPE_BOOL:
- result |= FIELD_TYPE_BOOL;
- break;
- case FieldDescriptorProto::TYPE_STRING:
- result |= FIELD_TYPE_STRING;
- break;
- case FieldDescriptorProto::TYPE_MESSAGE:
- result |= FIELD_TYPE_OBJECT;
- break;
- case FieldDescriptorProto::TYPE_BYTES:
- result |= FIELD_TYPE_BYTES;
- break;
- case FieldDescriptorProto::TYPE_UINT32:
- result |= FIELD_TYPE_UINT32;
- break;
- case FieldDescriptorProto::TYPE_ENUM:
- result |= FIELD_TYPE_ENUM;
- break;
- case FieldDescriptorProto::TYPE_SFIXED32:
- result |= FIELD_TYPE_SFIXED32;
- break;
- case FieldDescriptorProto::TYPE_SFIXED64:
- result |= FIELD_TYPE_SFIXED64;
- break;
- case FieldDescriptorProto::TYPE_SINT32:
- result |= FIELD_TYPE_SINT32;
- break;
- case FieldDescriptorProto::TYPE_SINT64:
- result |= FIELD_TYPE_SINT64;
- break;
- default:
- ;
- }
-
- // Count
- if (field.options().packed()) {
- result |= FIELD_COUNT_PACKED;
- } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
- result |= FIELD_COUNT_REPEATED;
- } else {
- result |= FIELD_COUNT_SINGLE;
- }
-
- return result;
-}
-
-/**
* Write a field.
*/
static void
diff --git a/tools/streaming_proto/stream.proto b/tools/streaming_proto/stream.proto
new file mode 100644
index 0000000..123506c
--- /dev/null
+++ b/tools/streaming_proto/stream.proto
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "google/protobuf/descriptor.proto";
+
+package android.stream_proto;
+
+// This option tells streaming proto plugin to compile .proto files with extra features.
+message StreamFlags {
+ // creates a mapping of field names of the message to its field ids
+ optional bool enable_fields_mapping = 1;
+}
+
+extend google.protobuf.MessageOptions {
+ // Flags used by streaming proto plugins
+ optional StreamFlags stream = 126856794;
+}
diff --git a/tools/streaming_proto/stream_proto_utils.cpp b/tools/streaming_proto/stream_proto_utils.cpp
new file mode 100644
index 0000000..e8f86bc
--- /dev/null
+++ b/tools/streaming_proto/stream_proto_utils.cpp
@@ -0,0 +1,102 @@
+#include "stream_proto_utils.h"
+
+namespace android {
+namespace stream_proto {
+
+/**
+ * Position of the field type in a (long long) fieldId.
+ */
+const uint64_t FIELD_TYPE_SHIFT = 32;
+
+//
+// FieldId flags for whether the field is single, repeated or packed.
+// TODO: packed is not supported yet.
+//
+const uint64_t FIELD_COUNT_SHIFT = 40;
+const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_UNKNOWN = 0;
+const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_PACKED = 5ULL << FIELD_COUNT_SHIFT;
+
+uint64_t
+get_field_id(const FieldDescriptorProto& field)
+{
+ // Number
+ uint64_t result = (uint32_t)field.number();
+
+ // Type
+ result |= (uint64_t)field.type() << FIELD_TYPE_SHIFT;
+
+ // Count
+ if (field.options().packed()) {
+ result |= FIELD_COUNT_PACKED;
+ } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
+ result |= FIELD_COUNT_REPEATED;
+ } else {
+ result |= FIELD_COUNT_SINGLE;
+ }
+
+ return result;
+}
+
+string
+get_proto_type(const FieldDescriptorProto& field)
+{
+ switch (field.type()) {
+ case FieldDescriptorProto::TYPE_DOUBLE:
+ return "double";
+ case FieldDescriptorProto::TYPE_FLOAT:
+ return "float";
+ case FieldDescriptorProto::TYPE_INT64:
+ return "int64";
+ case FieldDescriptorProto::TYPE_UINT64:
+ return "uint64";
+ case FieldDescriptorProto::TYPE_INT32:
+ return "int32";
+ case FieldDescriptorProto::TYPE_FIXED64:
+ return "fixed64";
+ case FieldDescriptorProto::TYPE_FIXED32:
+ return "fixed32";
+ case FieldDescriptorProto::TYPE_BOOL:
+ return "bool";
+ case FieldDescriptorProto::TYPE_STRING:
+ return "string";
+ case FieldDescriptorProto::TYPE_GROUP:
+ return "group<unsupported!>";
+ case FieldDescriptorProto::TYPE_MESSAGE:
+ return field.type_name();
+ case FieldDescriptorProto::TYPE_BYTES:
+ return "bytes";
+ case FieldDescriptorProto::TYPE_UINT32:
+ return "uint32";
+ case FieldDescriptorProto::TYPE_ENUM:
+ return field.type_name();
+ case FieldDescriptorProto::TYPE_SFIXED32:
+ return "sfixed32";
+ case FieldDescriptorProto::TYPE_SFIXED64:
+ return "sfixed64";
+ case FieldDescriptorProto::TYPE_SINT32:
+ return "sint32";
+ case FieldDescriptorProto::TYPE_SINT64:
+ return "sint64";
+ default:
+ // won't happen
+ return "void";
+ }
+}
+
+bool
+should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
+{
+ const int N = request.file_to_generate_size();
+ for (int i=0; i<N; i++) {
+ if (request.file_to_generate(i) == file) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // stream_proto
+} // android
diff --git a/tools/streaming_proto/stream_proto_utils.h b/tools/streaming_proto/stream_proto_utils.h
new file mode 100644
index 0000000..5297ecc
--- /dev/null
+++ b/tools/streaming_proto/stream_proto_utils.h
@@ -0,0 +1,29 @@
+#include <stdint.h>
+
+#include "google/protobuf/compiler/plugin.pb.h"
+#include "google/protobuf/io/zero_copy_stream_impl.h"
+
+namespace android {
+namespace stream_proto {
+
+using namespace google::protobuf;
+using namespace google::protobuf::compiler;
+using namespace std;
+
+/**
+ * Get encoded field id from a field.
+ */
+uint64_t get_field_id(const FieldDescriptorProto& field);
+
+/**
+ * Get the string name for a field.
+ */
+string get_proto_type(const FieldDescriptorProto& field);
+
+/**
+ * See if this is the file for this request, and not one of the imported ones.
+ */
+bool should_generate_for_file(const CodeGeneratorRequest& request, const string& file);
+
+} // stream_proto
+} // android
diff --git a/tools/streaming_proto/string_utils.h b/tools/streaming_proto/string_utils.h
index 03284d1..d6f195f 100644
--- a/tools/streaming_proto/string_utils.h
+++ b/tools/streaming_proto/string_utils.h
@@ -6,6 +6,9 @@
using namespace std;
+// Indent
+const string INDENT = " ";
+
/**
* Capitalizes the string, removes underscores and makes the next letter
* capitalized, and makes the letter following numbers capitalized.
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index a3a1054..551e4df 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -62,6 +62,8 @@
WifiConfiguration getMatchingWifiConfig(in ScanResult scanResult);
+ List<WifiConfiguration> getAllMatchingWifiConfigs(in ScanResult scanResult);
+
List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult);
int addOrUpdateNetwork(in WifiConfiguration config);
@@ -126,8 +128,6 @@
void releaseMulticastLock();
- void setWifiApEnabled(in WifiConfiguration wifiConfig, boolean enable);
-
void updateInterfaceIpState(String ifaceName, int mode);
boolean startSoftAp(in WifiConfiguration wifiConfig);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b08b4b7..c2959d5 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -68,6 +68,7 @@
* leaks within the calling process.
* <p>
* It deals with several categories of items:
+ * </p>
* <ul>
* <li>The list of configured networks. The list can be viewed and updated, and
* attributes of individual entries can be modified.</li>
@@ -79,9 +80,11 @@
* <li>It defines the names of various Intent actions that are broadcast upon
* any sort of change in Wi-Fi state.
* </ul>
+ * <p>
* This is the API to use when performing Wi-Fi specific operations. To perform
* operations that pertain to network connectivity at an abstract level, use
* {@link android.net.ConnectivityManager}.
+ * </p>
*/
@SystemService(Context.WIFI_SERVICE)
public class WifiManager {
@@ -1029,6 +1032,26 @@
}
/**
+ * Return all matching WifiConfigurations for this ScanResult.
+ *
+ * An empty list will be returned when no configurations are installed or if no configurations
+ * match the ScanResult.
+ *
+ * @param scanResult scanResult that represents the BSSID
+ * @return A list of {@link WifiConfiguration}
+ * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
+ * @hide
+ */
+ public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+ try {
+ return mService.getAllMatchingWifiConfigs(scanResult);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
* Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
*
* An empty list will be returned if no match is found.
@@ -1555,7 +1578,21 @@
* Request a scan for access points. Returns immediately. The availability
* of the results is made known later by means of an asynchronous event sent
* on completion of the scan.
- * @return {@code true} if the operation succeeded, i.e., the scan was initiated
+ * <p>
+ * To initiate a Wi-Fi scan, declare the
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}
+ * permission in the manifest, and perform these steps:
+ * </p>
+ * <ol style="1">
+ * <li>Invoke the following method:
+ * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).startScan()}</li>
+ * <li>
+ * Register a BroadcastReceiver to listen to
+ * {@code SCAN_RESULTS_AVAILABLE_ACTION}.</li>
+ * <li>When a broadcast is received, call:
+ * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).getScanResults()}</li>
+ * </ol>
+ * @return {@code true} if the operation succeeded, i.e., the scan was initiated.
*/
public boolean startScan() {
return startScan(null);
@@ -1857,32 +1894,6 @@
}
/**
- * This call is deprecated and removed. It is no longer used to
- * start WiFi Tethering. Please use {@link ConnectivityManager#startTethering(int, boolean,
- * ConnectivityManager#OnStartTetheringCallback)} if
- * the caller has proper permissions. Callers can also use the LocalOnlyHotspot feature for a
- * hotspot capable of communicating with co-located devices {@link
- * WifiManager#startLocalOnlyHotspot(LocalOnlyHotspotCallback)}.
- *
- * @param wifiConfig SSID, security and channel details as
- * part of WifiConfiguration
- * @return {@code false}
- *
- * @hide
- * @deprecated This API is nolonger supported.
- * @removed
- */
- @SystemApi
- @Deprecated
- @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
- public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
- String packageName = mContext.getOpPackageName();
-
- Log.w(TAG, packageName + " attempted call to setWifiApEnabled: enabled = " + enabled);
- return false;
- }
-
- /**
* Call allowing ConnectivityService to update WifiService with interface mode changes.
*
* The possible modes include: {@link IFACE_IP_MODE_TETHERED},
diff --git a/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl b/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl
index ad92e04..e1ad783 100644
--- a/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl
+++ b/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl
@@ -24,6 +24,7 @@
*/
interface IWifiRttManager
{
+ boolean isAvailable();
void startRanging(in IBinder binder, in String callingPackage, in RangingRequest request,
in IRttCallback callback);
}
diff --git a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
index 7405e82..c8aea3c4 100644
--- a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
+++ b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
@@ -36,7 +36,7 @@
*/
public abstract class RangingResultCallback {
/** @hide */
- @IntDef({STATUS_CODE_FAIL})
+ @IntDef({STATUS_CODE_FAIL, STATUS_CODE_FAIL_RTT_NOT_AVAILABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface RangingOperationStatus {
}
@@ -47,6 +47,14 @@
public static final int STATUS_CODE_FAIL = 1;
/**
+ * A failure code for the whole ranging request operation. Indicates that the request failed due
+ * to RTT not being available - e.g. Wi-Fi was disabled. Use the
+ * {@link WifiRttManager#isAvailable()} and {@link WifiRttManager#ACTION_WIFI_RTT_STATE_CHANGED}
+ * to track RTT availability.
+ */
+ public static final int STATUS_CODE_FAIL_RTT_NOT_AVAILABLE = 2;
+
+ /**
* Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
* devices specified in the request was attempted.
*
diff --git a/wifi/java/android/net/wifi/rtt/WifiRttManager.java b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
index 435bb37..c7c0923 100644
--- a/wifi/java/android/net/wifi/rtt/WifiRttManager.java
+++ b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
@@ -6,6 +6,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
import android.annotation.SystemService;
import android.content.Context;
import android.os.Binder;
@@ -22,11 +23,18 @@
* <p>
* The devices which can be ranged include:
* <li>Access Points (APs)
+ * <li>Wi-Fi Aware peers
* <p>
* Ranging requests are triggered using
* {@link #startRanging(RangingRequest, RangingResultCallback, Handler)}. Results (in case of
* successful operation) are returned in the {@link RangingResultCallback#onRangingResults(List)}
* callback.
+ * <p>
+ * Wi-Fi RTT may not be usable at some points, e.g. when Wi-Fi is disabled. To validate that
+ * the functionality is available use the {@link #isAvailable()} function. To track
+ * changes in RTT usability register for the {@link #ACTION_WIFI_RTT_STATE_CHANGED}
+ * broadcast. Note that this broadcast is not sticky - you should register for it and then
+ * check the above API to avoid a race condition.
*
* @hide RTT_API
*/
@@ -38,6 +46,18 @@
private final Context mContext;
private final IWifiRttManager mService;
+ /**
+ * Broadcast intent action to indicate that the state of Wi-Fi RTT availability has changed.
+ * Use the {@link #isAvailable()} to query the current status.
+ * This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
+ * the broadcast to check the current state of Wi-Fi RTT.
+ * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+ * components will be launched.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WIFI_RTT_STATE_CHANGED =
+ "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED";
+
/** @hide */
public WifiRttManager(Context context, IWifiRttManager service) {
mContext = context;
@@ -45,6 +65,22 @@
}
/**
+ * Returns the current status of RTT API: whether or not RTT is available. To track
+ * changes in the state of RTT API register for the
+ * {@link #ACTION_WIFI_RTT_STATE_CHANGED} broadcast.
+ *
+ * @return A boolean indicating whether the app can use the RTT API at this time (true) or
+ * not (false).
+ */
+ public boolean isAvailable() {
+ try {
+ return mService.isAvailable();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
* Results will be returned in the {@link RangingResultCallback} set of callbacks.
*
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index b235ccc7..ee6f12b 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -777,14 +777,4 @@
mWifiManager.unregisterLocalOnlyHotspotObserver();
verify(mWifiService).stopWatchLocalOnlyHotspot();
}
-
- /**
- * Verify that calls to setWifiApEnabled return false.
- */
- @Test
- public void testSetWifiApEnabledReturnsFalse() throws Exception {
- assertFalse(mWifiManager.setWifiApEnabled(null, true));
- assertFalse(mWifiManager.setWifiApEnabled(null, false));
- verify(mWifiService, never()).setWifiApEnabled(any(), anyBoolean());
- }
}